@windyroad/architect 0.17.2 → 0.17.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -169,10 +169,22 @@ if [ -z "$new_entry" ] || ! printf '%s' "$new_entry" | grep -qE '^### ADR-[0-9]+
|
|
|
169
169
|
exit 0
|
|
170
170
|
fi
|
|
171
171
|
|
|
172
|
+
# --- Capture pre-modification invariants for the fail-closed guard (P367) ---
|
|
173
|
+
# A single-entry re-author must change ONLY the edited ADR's entry. Snapshot the
|
|
174
|
+
# set of all ADR ids and the count of `## ` section headers, plus a backup of
|
|
175
|
+
# the whole file, so the post-condition guard below can detect (and reject) any
|
|
176
|
+
# silent tail truncation or spurious-id/section injection from the subprocess.
|
|
177
|
+
before_ids=$(grep -oE '^### ADR-[0-9]+' "$readme" | grep -oE '[0-9]+' | sed 's/^0*//' | sort -n -u)
|
|
178
|
+
before_sections=$(grep -cE '^## ' "$readme")
|
|
179
|
+
entry_existed=0
|
|
180
|
+
[ -n "$current_entry" ] && entry_existed=1
|
|
181
|
+
|
|
172
182
|
# --- Apply the entry: delete any existing block, then insert sorted ---------
|
|
173
183
|
tmp_entry=$(mktemp -t architect-entry.XXXXXX)
|
|
174
184
|
tmp_readme=$(mktemp -t architect-readme.XXXXXX)
|
|
175
|
-
|
|
185
|
+
backup_readme=$(mktemp -t architect-readme-orig.XXXXXX)
|
|
186
|
+
trap 'rm -f "$tmp_entry" "$tmp_readme" "$backup_readme"' EXIT
|
|
187
|
+
cp "$readme" "$backup_readme"
|
|
176
188
|
printf '%s\n' "$new_entry" > "$tmp_entry"
|
|
177
189
|
|
|
178
190
|
# Pass 1 — remove any existing block for this ADR-ID (and the single blank line
|
|
@@ -218,6 +230,26 @@ awk -v id="$adr_id" -v section="$target_section" -v entryfile="$tmp_entry" '
|
|
|
218
230
|
END { if (!done) { print ""; print entry } }
|
|
219
231
|
' "$tmp_readme" > "$readme"
|
|
220
232
|
|
|
233
|
+
# --- Fail-closed post-condition guard (P367, ADR-078 criterion l) -----------
|
|
234
|
+
# The rewrite must preserve every OTHER ADR's entry and the section structure;
|
|
235
|
+
# only the edited ADR's entry may change (it may be newly added). If the result
|
|
236
|
+
# dropped a pre-existing entry (silent tail truncation) or injected spurious ids
|
|
237
|
+
# or sections (malformed subprocess emit), restore the original and degrade —
|
|
238
|
+
# never stage a corrupted compendium. Same contract as the subprocess-failure
|
|
239
|
+
# path: exit 0, do not block the body edit; Story B's pairing check surfaces it.
|
|
240
|
+
after_ids=$(grep -oE '^### ADR-[0-9]+' "$readme" | grep -oE '[0-9]+' | sed 's/^0*//' | sort -n -u)
|
|
241
|
+
after_sections=$(grep -cE '^## ' "$readme")
|
|
242
|
+
expected_ids="$before_ids"
|
|
243
|
+
if [ "$entry_existed" -eq 0 ]; then
|
|
244
|
+
expected_ids=$(printf '%s\n%s\n' "$before_ids" "$adr_id" | sed '/^$/d' | sort -n -u)
|
|
245
|
+
fi
|
|
246
|
+
edited_count=$(grep -oE '^### ADR-[0-9]+' "$readme" | grep -oE '[0-9]+' | sed 's/^0*//' | grep -cxF "$adr_id")
|
|
247
|
+
if [ "$after_ids" != "$expected_ids" ] || [ "$after_sections" != "$before_sections" ] || [ "$edited_count" -ne 1 ]; then
|
|
248
|
+
cp "$backup_readme" "$readme"
|
|
249
|
+
echo "architect-compendium-update-entry: post-condition guard tripped for ADR-${adr_id} (compendium entry-set or section drift — possible truncation or spurious injection); restored README unchanged (degraded mode), not staged. Recover with wr-architect-generate-decisions-compendium && git add docs/decisions/README.md" >&2
|
|
250
|
+
exit 0
|
|
251
|
+
fi
|
|
252
|
+
|
|
221
253
|
# Stage the compendium so it lands in the same commit as the ADR body change.
|
|
222
254
|
( cd "$project_dir" && git add docs/decisions/README.md 2>/dev/null ) || \
|
|
223
255
|
echo "architect-compendium-update-entry: git add docs/decisions/README.md failed (not a git repo or staging error) — stage it manually before commit" >&2
|
|
@@ -234,6 +234,64 @@ run_hook() {
|
|
|
234
234
|
[ "$status" -eq 0 ]
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
@test "fail-closed guard: rejects a subprocess entry that injects spurious ADR ids/sections — restores README, degraded, unstaged (P367)" {
|
|
238
|
+
mk_readme
|
|
239
|
+
( cd "$PROJ" && git add -A && git commit -q -m init )
|
|
240
|
+
before=$(cat "$PROJ/docs/decisions/README.md")
|
|
241
|
+
fp=$(mk_adr "049" "accepted" "FortyNine")
|
|
242
|
+
# Malformed shim: valid header for the edited id, but ALSO injects an
|
|
243
|
+
# unrelated ADR-999 header and a spurious '## ' section — the additive
|
|
244
|
+
# corruption shape empirically reproduced for P367.
|
|
245
|
+
BADDIR="$(mktemp -d)"
|
|
246
|
+
cat > "$BADDIR/claude" <<'SHIM'
|
|
247
|
+
#!/usr/bin/env bash
|
|
248
|
+
cat >/dev/null
|
|
249
|
+
entry="### ADR-049 — Hijacked
|
|
250
|
+
**Status:** accepted | **Oversight:** confirmed
|
|
251
|
+
**Decides:** body.
|
|
252
|
+
|
|
253
|
+
## Injected section
|
|
254
|
+
|
|
255
|
+
### ADR-999 — Sneaky"
|
|
256
|
+
jq -cn --arg r "$entry" '{result:$r}'
|
|
257
|
+
SHIM
|
|
258
|
+
chmod +x "$BADDIR/claude"
|
|
259
|
+
export PATH="$BADDIR:$ORIG_PATH"
|
|
260
|
+
run run_hook "$fp"
|
|
261
|
+
[ "$status" -eq 0 ] # never blocks the body edit
|
|
262
|
+
[ "$before" = "$(cat "$PROJ/docs/decisions/README.md")" ] # restored unchanged
|
|
263
|
+
! grep -q 'ADR-999' "$PROJ/docs/decisions/README.md" # no injected id survives
|
|
264
|
+
[[ "$output" == *"guard"* ]] # observable degraded signal
|
|
265
|
+
# README left in its committed state — no corrupted blob staged.
|
|
266
|
+
( cd "$PROJ" && git diff --cached --quiet -- docs/decisions/README.md )
|
|
267
|
+
rm -rf "$BADDIR"
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@test "fail-closed guard: rejects an emit for the wrong ADR id (edited id absent) — restores, degraded (P367)" {
|
|
271
|
+
mk_readme
|
|
272
|
+
before=$(cat "$PROJ/docs/decisions/README.md")
|
|
273
|
+
fp=$(mk_adr "050" "proposed" "Fifty") # NEW adr — not yet in the compendium
|
|
274
|
+
# Shim emits an entry for the WRONG id (049, which already exists) instead of
|
|
275
|
+
# the edited 050: the edited id never lands and 049 is duplicated.
|
|
276
|
+
WRONGDIR="$(mktemp -d)"
|
|
277
|
+
cat > "$WRONGDIR/claude" <<'SHIM'
|
|
278
|
+
#!/usr/bin/env bash
|
|
279
|
+
cat >/dev/null
|
|
280
|
+
entry="### ADR-049 — WrongId
|
|
281
|
+
**Status:** proposed | **Oversight:** confirmed
|
|
282
|
+
**Decides:** body."
|
|
283
|
+
jq -cn --arg r "$entry" '{result:$r}'
|
|
284
|
+
SHIM
|
|
285
|
+
chmod +x "$WRONGDIR/claude"
|
|
286
|
+
export PATH="$WRONGDIR:$ORIG_PATH"
|
|
287
|
+
run run_hook "$fp"
|
|
288
|
+
[ "$status" -eq 0 ]
|
|
289
|
+
[ "$before" = "$(cat "$PROJ/docs/decisions/README.md")" ] # restored unchanged
|
|
290
|
+
! grep -q '^### ADR-050' "$PROJ/docs/decisions/README.md" # edited id never landed
|
|
291
|
+
[[ "$output" == *"guard"* ]]
|
|
292
|
+
rm -rf "$WRONGDIR"
|
|
293
|
+
}
|
|
294
|
+
|
|
237
295
|
@test "registered in hooks.json on PostToolUse Edit|Write (criterion 9)" {
|
|
238
296
|
HOOKS_JSON="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/hooks.json"
|
|
239
297
|
run jq -e '.hooks.PostToolUse[] | select(.matcher | test("Edit")) | .hooks[] | select(.command | test("architect-compendium-update-entry"))' "$HOOKS_JSON"
|