Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ⛔ THE EXECUTABLE ARTIFACTS ARE LF, IN THE WORKING TREE, ON EVERY PLATFORM.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Sentinel — findings outside this diff's added lines

These are in files this pull request changes, but on lines it does not add, so they cannot be commented in place. They are posted here as one thread so they can be answered or resolved rather than merged past.

  • 🟠 High · subprocess shell true scripts/check-handoff-rows.py:218
    Found 'subprocess' function 'run' with 'shell=True'. This is dangerous because this call will spawn the command using a shell process. Doing so propagates current shell settings and variables, which makes it much easier for a malicious actor to execute commands. Use 'shell=False'…

Resolve this thread once each is fixed or judged not to apply.

#
# Measured on a Windows 11 / Git Bash install (#502 B3): `scripts/fleet-preflight.sh:
# line 10: $'\r': command not found`. The committed blobs were already LF — but with
# `core.autocrlf=true`, which is the Git-for-Windows DEFAULT, the WORKING TREE is CRLF,
# and bash executes the working tree, not the blob.
#
# ⇒ Two consequences, and the second is why a one-time `dos2unix` is not the fix:
# · the estate's own clone is CRLF for the same reason, so a plain `cp` during
# onboard.md step 2 carries CR into the target repository
# · without this file the NEXT checkout re-breaks it, silently
#
# ⚠ This pins the working tree, not just the blob. `text eol=lf` is what does that;
# `-text` (binary) would also stop conversion but would break diffs.
*.sh text eol=lf
*.py text eol=lf
*.json text eol=lf
*.md text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
2 changes: 1 addition & 1 deletion prompts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ prints the expected roster as a checklist.

It exists because **nine agents asserting they are ready is not the same as nine agents being
ready**. The preflight establishes by execution what the agent panes each claim in prose, so the
claims have something to be checked against. It reports and never gates: exit code is always 0.
claims have something to be checked against. It does not gate the fleet — no pane waits on it — but **its exit code carries a verdict**: `0` clean · `1` blocking failures · `2` it could not establish its own. ⚠ This sentence read *"exit code is always 0"* for as long after the change as it took an outside installer to notice (#502 C4); `onboard.md` makes this the acceptance test for an install, and an acceptance test that cannot fail is worse than none.

### What this recipe cannot do

Expand Down
14 changes: 14 additions & 0 deletions scripts/check-goal-conformance.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@
"""
import json, os, re, subprocess, sys

# ⛔ THESE SCRIPTS CRASH EXACTLY WHEN THEY FIND SOMETHING. Windows Python defaults stdout
# to cp1252, and the ⛔/⚠/★ glyphs appear almost only on FAIL branches — so a checker runs
# clean when all is well and dies with UnicodeEncodeError when it detects a defect, and a
# crashed checker reports nothing at all (#502 B4, measured on a Windows 11 install).
# ⇒ errors="replace" rather than a hard switch: a mangled glyph is a legible finding, an
# exception is not.
# ⚠ EACH STREAM GUARDED SEPARATELY. `hasattr(sys.stdout, ...)` says nothing about
# sys.stderr — a harness may replace one and not the other (this repo's own stubbed
# suites capture streams), and the guarded form would then raise AttributeError from
# inside the guard meant to prevent one.
for _stream in (sys.stdout, sys.stderr):
if hasattr(_stream, "reconfigure"):
_stream.reconfigure(encoding="utf-8", errors="replace")

# The six, from goals/README.md "What a role goal must contain". Each entry is
# (label, regex over headings). ⚠ Matched on HEADINGS, not on body text: a file
# that merely discusses "reserved actions" in prose has not stated them where every
Expand Down
14 changes: 14 additions & 0 deletions scripts/check-handoff-rows.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "tools"))
from runmarker import guard, result # noqa: E402

# ⛔ THESE SCRIPTS CRASH EXACTLY WHEN THEY FIND SOMETHING. Windows Python defaults stdout
# to cp1252, and the ⛔/⚠/★ glyphs appear almost only on FAIL branches — so a checker runs
# clean when all is well and dies with UnicodeEncodeError when it detects a defect, and a
# crashed checker reports nothing at all (#502 B4, measured on a Windows 11 install).
# ⇒ errors="replace" rather than a hard switch: a mangled glyph is a legible finding, an
# exception is not.
# ⚠ EACH STREAM GUARDED SEPARATELY. `hasattr(sys.stdout, ...)` says nothing about
# sys.stderr — a harness may replace one and not the other (this repo's own stubbed
# suites capture streams), and the guarded form would then raise AttributeError from
# inside the guard meant to prevent one.
for _stream in (sys.stdout, sys.stderr):
if hasattr(_stream, "reconfigure"):
_stream.reconfigure(encoding="utf-8", errors="replace")

HANDOFF = "docs/HANDOFF.md"

# ⇒ The command starts at the first shell head. This is what makes the row splittable
Expand Down
14 changes: 14 additions & 0 deletions scripts/check-onboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@
import sys
from pathlib import Path

# ⛔ THESE SCRIPTS CRASH EXACTLY WHEN THEY FIND SOMETHING. Windows Python defaults stdout
# to cp1252, and the ⛔/⚠/★ glyphs appear almost only on FAIL branches — so a checker runs
# clean when all is well and dies with UnicodeEncodeError when it detects a defect, and a
# crashed checker reports nothing at all (#502 B4, measured on a Windows 11 install).
# ⇒ errors="replace" rather than a hard switch: a mangled glyph is a legible finding, an
# exception is not.
# ⚠ EACH STREAM GUARDED SEPARATELY. `hasattr(sys.stdout, ...)` says nothing about
# sys.stderr — a harness may replace one and not the other (this repo's own stubbed
# suites capture streams), and the guarded form would then raise AttributeError from
# inside the guard meant to prevent one.
for _stream in (sys.stdout, sys.stderr):
if hasattr(_stream, "reconfigure"):
_stream.reconfigure(encoding="utf-8", errors="replace")

ROOT = Path(__file__).resolve().parent.parent
DOC = ROOT / "onboard.md"
RECIPE = ROOT / ".daintree" / "recipes" / "nforma-fleet.json"
Expand Down
14 changes: 14 additions & 0 deletions scripts/check-orientation.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@
import sys
from pathlib import Path

# ⛔ THESE SCRIPTS CRASH EXACTLY WHEN THEY FIND SOMETHING. Windows Python defaults stdout
# to cp1252, and the ⛔/⚠/★ glyphs appear almost only on FAIL branches — so a checker runs
# clean when all is well and dies with UnicodeEncodeError when it detects a defect, and a
# crashed checker reports nothing at all (#502 B4, measured on a Windows 11 install).
# ⇒ errors="replace" rather than a hard switch: a mangled glyph is a legible finding, an
# exception is not.
# ⚠ EACH STREAM GUARDED SEPARATELY. `hasattr(sys.stdout, ...)` says nothing about
# sys.stderr — a harness may replace one and not the other (this repo's own stubbed
# suites capture streams), and the guarded form would then raise AttributeError from
# inside the guard meant to prevent one.
for _stream in (sys.stdout, sys.stderr):
if hasattr(_stream, "reconfigure"):
_stream.reconfigure(encoding="utf-8", errors="replace")

ROOT = Path(__file__).resolve().parent.parent
DOC = ROOT / "CLAUDE.md"

Expand Down
14 changes: 14 additions & 0 deletions scripts/check-tools-index.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@
import sys
from pathlib import Path

# ⛔ THESE SCRIPTS CRASH EXACTLY WHEN THEY FIND SOMETHING. Windows Python defaults stdout
# to cp1252, and the ⛔/⚠/★ glyphs appear almost only on FAIL branches — so a checker runs
# clean when all is well and dies with UnicodeEncodeError when it detects a defect, and a
# crashed checker reports nothing at all (#502 B4, measured on a Windows 11 install).
# ⇒ errors="replace" rather than a hard switch: a mangled glyph is a legible finding, an
# exception is not.
# ⚠ EACH STREAM GUARDED SEPARATELY. `hasattr(sys.stdout, ...)` says nothing about
# sys.stderr — a harness may replace one and not the other (this repo's own stubbed
# suites capture streams), and the guarded form would then raise AttributeError from
# inside the guard meant to prevent one.
for _stream in (sys.stdout, sys.stderr):
if hasattr(_stream, "reconfigure"):
_stream.reconfigure(encoding="utf-8", errors="replace")

# ⇒ DEV5 wrote this block for #348; DEVOPS owns the file and this is the import site
# offered for review. The shared predicate lives in tools/ so the two guards cannot
# disagree about the same file — one module, referenced, never copied.
Expand Down
13 changes: 10 additions & 3 deletions scripts/fleet-preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
# panes will each *claim* in their ROLE-READY line — so those claims have
# something to be checked against.
#
# Exit code is always 0: this pane reports, it does not gate.
# ⛔ EXIT CODE CARRIES THE VERDICT: 0 clean · 1 blocking failures · 2 the script could
# not establish its own verdict. It said "always 0" here for as long as that was true and
# for a while after it stopped being (#502 C4) — see the reasoning at the Summary section.

ROLES=(TEAMLEAD ARCHITECT DEVOPS DX DEV1 DEV2 DEV3 DEV4 DEV5)

Expand All @@ -31,7 +33,10 @@ else
# ⛔ NOT basename "$toplevel" — in a worktree that is the worktree's directory
# name ("devops"), not the repository ("nForma-NEXT"). The main tree is always
# the first entry of `git worktree list --porcelain`.
main_tree=$(git worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2; exit}')
# ⛔ NOT `awk '{print $2}'` — `--porcelain` puts the path in the REST of the line, so
# field 2 truncates at the first space (#502 C2). Fixed in fleet-worktree.sh first;
# this second site was found by sweeping for the pattern rather than the file.
main_tree=$(git worktree list --porcelain 2>/dev/null | sed -n '1s/^worktree //p')
repo=$(basename "${main_tree:-$toplevel}")
branch=$(git branch --show-current 2>/dev/null)
ok "repo=$repo branch=${branch:-<detached>} — THIS TREE ONLY"
Expand Down Expand Up @@ -233,7 +238,9 @@ section 'Repository self-checks'
# them — and that one was a fixture whose header says "Not run; scanned." A set of
# instruments none of which is ever called is a citation network, not a toolchain.
# These two are cheap, deterministic, and answer questions no reviewer reliably
# answers by eye. ⚠ This pane still does not gate: exit code is always 0.
# answers by eye. ⚠ This pane does not gate the FLEET, but it DOES carry a verdict in its
# exit code (0/1/2) — a caller may branch on it. #502 C4 found this line and two others
# still claiming "always 0" after the behaviour was deliberately changed.
# ⇒ check-handoff-rows.py joins this loop because THE DEFECT IT CATCHES SHIPPED
# (#637): a snapshot row named `--by-state`, a flag that prints no totals, so three
# correct numbers sat under a command that cannot produce any of them. A review bot
Expand Down
10 changes: 8 additions & 2 deletions scripts/fleet-worktree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ set -u

ROLES="architect devops dx dev1 dev2 dev3 dev4 dev5"

main_tree=$(git worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2; exit}')
# ⛔ `awk '{print $2}'` TRUNCATES AT THE FIRST SPACE. `--porcelain` emits `worktree <path>`
# with the path as the REST of the line, not as field 2 — so `C:\Program Files\repo`
# becomes `C:\Program`, silently, and every path derived from it is wrong. Not
# hypothetical on Windows, where that directory is a normal location (#502 C2).
# ⇒ `sed` strips the known prefix and keeps everything after it.
main_tree=$(git worktree list --porcelain 2>/dev/null | sed -n '1s/^worktree //p')
Comment thread
coderabbitai[bot] marked this conversation as resolved.
[ -n "$main_tree" ] || { echo "not inside a git repository" >&2; exit 2; }
WT_DIR="$main_tree/.claude/worktrees"

Expand All @@ -52,7 +57,8 @@ WT_DIR="$main_tree/.claude/worktrees"
# MISSING no tree at all -> create it
where() {
git worktree list --porcelain | awk -v want="$WT_DIR/$1" -v role="$1" '
/^worktree /{ p = $2
# ⛔ substr, NOT $2 — same truncation as the main_tree parse above, third site.
/^worktree /{ p = substr($0, 10)
if (p == want) { found = 1; next }
# HEURISTIC, and it is one: a path whose last element contains the role
# token is PROBABLY that tree. Nothing binds a worktree to a role, so this
Expand Down
10 changes: 10 additions & 0 deletions scripts/validate-recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ def validate(path):
except (OSError, json.JSONDecodeError) as exc:
return [f"{path}: unreadable or invalid JSON: {exc}"], [], None

# ⛔ VALID JSON IS NOT AN OBJECT. `json.load` accepts any JSON *value* — a top-level
# list, string, number or null all parse cleanly and then reach `recipe.get`, which
# raises AttributeError. Measured 2026-09-07 on `[]`: traceback, exit 1, no report.
# ⇒ For a validator whose entire purpose is REFUSING malformed input, crashing
# instead of reporting is the wrong failure mode — the caller cannot tell a rejected
# recipe from a broken validator. (#502 C1, raised by an external reviewer.)
if not isinstance(recipe, dict):
return [f"{path}: top-level JSON is {type(recipe).__name__}, not an object — "
f"a recipe must be a JSON object"], [], None

for field in ("id", "name"):
if not isinstance(recipe.get(field), str) or not recipe[field]:
errs.append(f"{field} must be a non-empty string")
Expand Down
Loading