* docs(#3469): amend ADR-3408 section 8.3 — the pipeline has sanctioned exceptions Section 8.3 read 'Every STATE.md write applies the pipeline.' That is false by design for two commands, and acting on it would have inverted a shipped feature. Preservation makes curated frontmatter win over a re-derived body value. state sync exists to do the opposite — #905's 'body annotation beats existing frontmatter when both are present'; it re-derives frontmatter FROM the body. REGENERATE_STATE is a factory reset that rebuilds STATE.md from scratch. Applying the pipeline to either would re-lock exactly what the command was invoked to replace. This issue's own scope line, inherited from the epic, said to route the direct writeStateMd callers through the pipeline. For cmdStateSync that would have shipped silently, with every gate green, because no test asserts that sync LETS the body win. Caught by reading the helper's docstring and then verifying the claim against the code — a stale comment had already misdirected this epic once. Both commands are now named in a closed exception list and are permanent ratchet entries. Consequence recorded rather than left to bite Phase 4: the 'drive the ratchet to 0 and delete the file' target in this ADR and in #3471 is wrong. Two entries are permanent, so the correct end state is 2, and the honest report is '0 removable bypasses, 2 sanctioned'. A guard reaching 0 here would only do so by having stopped looking at two real writers. * refactor(#3469): one composition for the write seam, not one per caller Implements ADR-3408 section 8.3 as amended. syncAndPreserveStateMd is now the single composition of syncStateFrontmatter and applyPostSyncPreservation. readModifyWriteStateMd and cmdPhaseComplete both CALL it instead of each assembling the two steps themselves. cmdPhaseComplete keeps its own writePlanningFileSet envelope — the composition returns content, it does not take over the write, so STATE.md still commits atomically with ROADMAP and REQUIREMENTS. Assembling the stages at a call site is a re-derivation even when every step calls an owner. Upstream's fix(#3374) routed cmdPhaseComplete through applyPostSyncPreservation but left it calling syncStateFrontmatter directly first, so the composition was duplicated and free to diverge with both guards green. That is ADR-3180 Amendment 2's finding repeating on the write side. cmdMilestoneComplete gains preservation. It wrote through writeStateMd, so it got sync and no preservation — the identical shape #3374 reported for phase.complete, and flagged upstream as a follow-up in the helper's own docstring. This is that follow-up. Divergence is now visible: preservation_warnings names each field restored over a disagreeing derived value. Deliberately NOT named warnings — cmdPhaseComplete already exposes warnings as a prose string array, and two sibling commands carrying that name with different element types is Generative Fix Divergence, the class this epic exists to remove. patchCore stops running stateReplaceField over the whole document. One observable consequence, intended per design row 9: a frontmatter-shaped patch key with no body counterpart now reports failed instead of silently succeeding, because the old whole-document match was literally hitting the YAML line case-insensitively. The guard closes Phase 1's DECLARED KNOWN GAP as promised rather than re-deferring it: section 8.3(b) detection is tractable now the composition exists. Scoped by two factors to avoid Phase 1's measured 29-to-1 false positive rate — a variable field-name argument AND a content argument whose nearest preceding assignment is not stripFrontmatter. Verified 0 findings and 0 false positives across all 33 call sites, plus 5 synthetic shapes. It also detects the re-assembly shape above. Ratchet: 4 entries to 2, both sanctioned-permanent. cmdStateSync's owner changes from #3471 to sanctioned-permanent per Amendment 2 — routing it through preservation would invert the #905 contract. Also fixed inline rather than deferred: cmdMilestoneComplete's STATE.md read now happens inside withStateLock. It previously read outside any lock before writeStateMd took its own, leaving a TOCTOU window under concurrent writers. * test(#3469): characterization coverage for the single write seam Matrix sections A-E. Criterion 6 was amended by maintainer decision — all five instances closed by point fixes while Phase 1 was in flight — so these are characterization tests at the consumer's output per ADR-3180 Decision 4(b)/(c), paired with the drift guard's count, never either alone. Section C is the one that earns its keep. cmdStateSync is a sanctioned permanent exception: state sync exists to re-derive frontmatter FROM the body, so preservation there re-locks exactly what the command was invoked to replace. C1 pins that the body wins; C4 pins that this phase left the command byte-identical. Nothing else in the suite would notice if a future change made sync start preserving, and the natural reading of 'one write seam' is to make precisely that change. Section E pins the guard's false-positive scoping. E4 (updateCore's strip-then-replace) and E5 (sectionBody-scoped calls) must NOT be reported — the naive detector measured 29 false positives to 1 true positive in Phase 1. E7 is the inverse: a sanctioned-permanent entry disappearing must FAIL, because a guard reaching zero here would only do so by having stopped looking at two real writers. Also corrects a stale test that asserted patchCore's old whole-document behavior, which this phase deliberately changes. One honest limitation, flagged rather than papered over: A1's 'byte-identical to pre-refactor' cannot be diffed against real pre-refactor bytes from inside the suite. It is implemented as the seeded fast-check property that cmdPhaseComplete's composed output equals readModifyWriteStateMd's for the same inputs — the strongest available proxy, not the literal claim. * docs(#3469): refresh the seam glossary entry and add the changeset Two spec-review gaps, both real. CONTEXT.md's STATE.md Transition Module entry named three direct writeStateMd callers including cmdMilestoneComplete. This phase routed that one through the composition, so the line was false the moment the refactor landed. Worth recording plainly: I wrote that sentence in Phase 0, correcting an older stale pointer in it, and my own Phase 2 change invalidated it again within the same epic. That is the exact drift this epic exists to remove, demonstrated on the epic's own documentation — and it is why the entry now ends by saying the whole-repo drift guard, not this line, is the authoritative count. The entry now records the composition (syncAndPreserveStateMd) and states that exactly two direct callers remain, both SANCTIONED PERMANENT rather than debt. Changeset: type Changed, because milestone complete's observable output moves. Tier-2 per ADR-3180 Decision 3 — a stale body line no longer wins over fresher frontmatter, and the command gains preservation_warnings. Docs requirement is met by the ADR amendment already in this diff. * test(#3469): register property-test temp-dir cleanup at creation time Standards review, minor but real: the new fast-check property cleaned up its temp dirs in a loop AFTER fc.assert returned. A genuine property failure throws, so that line never ran and every dir from the failing run — including all of fast-check's shrinking iterations — leaked. The failure path is exactly when a littered machine hurts most, and a failing property test is the case the test exists for. Cleanup is now registered with t.after() at dir-creation time, so teardown happens however the test exits. Not try/finally — CONTRIBUTING.md:356 bans it inside test bodies, which is why the after-the-assertion shape existed in the first place. Swept the rest of the branch's test diff for the same shape; phase.test.cjs already uses registered teardown and nothing else matched. * fix(#3469): patchCore routes frontmatter writes instead of dropping them Checkpoint returned 10 failures of 33880. One implementation defect, three test defects, one stale test — all fixed, and the implementation defect is the one that matters. patchCore stripped frontmatter and then reconstructed it VERBATIM, applying no patches to it. An arbitrary custom frontmatter key with no body counterpart and no FIELD_CLASSIFICATION row — risk_level in the upstream fix(#3351) test — therefore always reported failed and silently never wrote. It worked before, via the old whole-document match on the raw YAML line. That is a regression against this phase's own design row 9, which requires frontmatter changes to ROUTE THROUGH the seam — still work, policy-governed — not to stop working. Removing a capability is not routing it. An upstream test caught it, which is the argument for running the checkpoint before believing the refactor. patchCore now partitions by frontmatter shape, decided structurally from the parsed frontmatter's own keys rather than a naming heuristic: - classified keys still report failed — policy owns them and a raw patch may not bypass it; - unclassified keys apply to the frontmatter object and report updated — Phase 1's behavior-table row 19, a field with no row is not this contract's business; - body-shaped keys are unchanged. The property 'failure' was my own test breaking the repo's Clock Seams rule. The two paths agree byte-for-byte; the only difference was last_updated, stamped from the wall clock on two invocations milliseconds apart, so it could never pass. Time is now frozen with mock.timers across both — not by excluding last_updated from the comparison, which would have silently stopped comparing a field the composition writes. B4's fixture could not discriminate: normalizeStateStatus maps any text containing 'complete' to 'completed', and milestone complete's own new body value derives to exactly that — which was also the fixture's stale value. The stale value is now 'executing' so the assertion can tell 'body correctly won' from 'stale survived'. B5's fixture tripped a pre-existing unstarted-phase guard before reaching any write-seam code; it now has the matching phase directory. D9 asserted the old exempt set. readModifyWriteStateMd now calls one symbol rather than assembling two, so it needs no exemption; syncAndPreserveStateMd is the sole legitimate composition site. * fix(#3469): patchCore resolves body-first, so the body wins a name collision Re-verification returned 2 failures of 33880, both D4 — the hostile row for a key that exists as BOTH a frontmatter key and a body field. The partition checked frontmatter first, so 'status' — classified in FIELD_CLASSIFICATION and also present as a body 'Status:' line — routed to the frontmatter branch, was rejected as classified, and reported failed. Wrong order. Patching 'status' means the body field, and upstream fix(#3351) says so in its own comment: 'the legitimate working case for state.patch is display-cased BODY fields — Status, Current Plan, Phase.' The body is authoritative in this model; frontmatter is the projection. D4 asserted exactly that and was right. Resolution order is now body, then frontmatter: 1. resolves to a body field -> apply to body, updated 2. else an own key of the frontmatter: classified -> failed (policy owns it) unclassified -> apply to frontmatter, updated 3. else -> failed Verified by probe against the compiled lib for all four cases rather than asserted: risk_level (frontmatter-only, unclassified) still lands; current_phase still fails; display-cased Status unchanged; D4's lower-cased status now lands via the body with the frontmatter untouched. The current_phase case was the one that could have regressed silently, so its fixture was read rather than assumed — D1's body carries 'Phase: 3 (alpha)' and no 'Current Phase:' line, so body-first cannot reach it. * chore(#3469): backfill pr number in changeset fragment --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -62,35 +62,33 @@
|
||||
* to prevent. `stripComments` does not track quoted strings for exactly
|
||||
* this reason — see its own header.
|
||||
*
|
||||
* DECLARED KNOWN GAP — §8.3(b) `patchCore` frontmatter-write shape is NOT
|
||||
* detected by this guard. `patchCore` runs `stateReplaceField(` over the
|
||||
* WHOLE document (body + frontmatter) instead of stripping frontmatter
|
||||
* first, the way `updateCore` does — a real defect, but this guard does not
|
||||
* catch it.
|
||||
* AXIS 3 — FRONTMATTER-SHAPED WRITE (§8.3(b)), CLOSED IN PHASE 2 (#3469).
|
||||
* Phase 1 left this as a DECLARED KNOWN GAP: `patchCore` ran
|
||||
* `stateReplaceField(` over the WHOLE document (body + frontmatter) instead
|
||||
* of stripping frontmatter first, the way `updateCore` does, and a naive
|
||||
* co-occurrence approximation ("does the enclosing function also call
|
||||
* `stripFrontmatter(`?") measured at 33 occurrences of `stateReplaceField(`,
|
||||
* of which only 4 were genuine write-seam bypasses and 29 were noise — the
|
||||
* definition of `stateReplaceField` itself, ~20 calls on `sectionBody` (a
|
||||
* body slice that is frontmatter-free by construction), and several calls
|
||||
* inside `readModifyWriteStateMd` callbacks. 29 false positives to 1 true
|
||||
* positive would have buried the signal.
|
||||
*
|
||||
* Why: catching it needs genuine DATAFLOW ("is this argument a variable
|
||||
* holding the full document, or a body slice?"), not function-scoped
|
||||
* co-occurrence. A co-occurrence approximation (does the enclosing function
|
||||
* also call `stripFrontmatter(`?) was implemented and measured directly
|
||||
* against this repo: 33 occurrences of `stateReplaceField(`, of which only
|
||||
* 4 are genuine write-seam bypasses and 29 are noise — the definition of
|
||||
* `stateReplaceField` itself (matched as a call), ~20 calls on `sectionBody`
|
||||
* (a body slice that is frontmatter-free by construction), and several
|
||||
* calls inside `readModifyWriteStateMd` callbacks (correct, because the RMW
|
||||
* envelope applies preservation after the callback returns). 29 false
|
||||
* positives to 1 true positive buries the signal and makes the ratchet's
|
||||
* shrink-rate meaningless as a Phase 2 progress indicator — recorded here,
|
||||
* with these numbers, so the next reader does not re-attempt the same
|
||||
* approximation.
|
||||
*
|
||||
* Who owns closing it: Phase 2 (#3469), which also FIXES the defect by
|
||||
* consolidating on the single write seam — after which detection becomes
|
||||
* tractable, because once the pure pipeline exists the invariant simplifies
|
||||
* to "no transition core calls `stateReplaceField` on unstripped content".
|
||||
*
|
||||
* This is a DECLARED gap with a named owner, not a silent omission — a
|
||||
* guard that quietly does not look somewhere is the failure ADR-3180
|
||||
* Decision 4(d) records.
|
||||
* Phase 2 fixes `patchCore` (it now strips frontmatter first, matching
|
||||
* `updateCore`) AND closes the gap, using a narrower, two-factor shape that
|
||||
* does not reproduce that ratio: `findUnstrippedContentWrites` below flags a
|
||||
* `stateReplaceField(` call only when BOTH (a) its field-name argument is a
|
||||
* VARIABLE, not a fixed string literal — every OTHER call site in
|
||||
* `EXECUTOR_FILE` passes a fixed Title-Case literal (`'Phase'`, `'Total
|
||||
* Plans in Phase'`, ...) that can never collide with a lowercase/snake_case
|
||||
* YAML frontmatter key, so a literal field name is never a candidate
|
||||
* regardless of whether its content argument is stripped — and (b) its
|
||||
* content argument has not been run through `stripFrontmatter` first,
|
||||
* checked by a simple backward scan (within the same function) for the
|
||||
* nearest preceding assignment to that argument's variable name. This is
|
||||
* deliberately NOT full alias/dataflow tracking — see the function's own
|
||||
* docstring for the narrow, documented limitation this trades for
|
||||
* tractability.
|
||||
*/
|
||||
|
||||
const fs = require('node:fs');
|
||||
@@ -108,6 +106,10 @@ const BASELINE_PATH = path.join(__dirname, 'state-write-path-drift-baseline.json
|
||||
const REASON = Object.freeze({
|
||||
FIELD_NAME_DISPATCH: 'field_name_dispatch',
|
||||
UNIMPLEMENTED_POLICY: 'unimplemented_policy',
|
||||
// Axis 3 (§8.3(b), closed Phase 2 / #3469): a `stateReplaceField(` call
|
||||
// with a variable field-name argument whose content argument was not run
|
||||
// through `stripFrontmatter` first — see `findUnstrippedContentWrites`.
|
||||
UNSTRIPPED_CONTENT_WRITE: 'unstripped_content_write',
|
||||
SEAM_BYPASS_UNRECORDED: 'seam_bypass_unrecorded',
|
||||
SEAM_BYPASS_COUNT_GREW: 'seam_bypass_count_grew',
|
||||
SEAM_BYPASS_COUNT_SHRANK: 'seam_bypass_count_shrank',
|
||||
@@ -132,12 +134,22 @@ const EXECUTOR_FILE = 'src/state-transition.cts';
|
||||
const SEAM_OWNER_FILE = 'src/state.cts';
|
||||
|
||||
// Per Decision 4(d)'s "owner FILE is not exempt, only its named canonical
|
||||
// FUNCTIONS are": a `writeStateMd(`/`syncStateFrontmatter(` call inside one
|
||||
// of these two functions, in `SEAM_OWNER_FILE` only, is the seam's own
|
||||
// internal plumbing (the I/O wrapper calling the pure sync stage), not a
|
||||
// bypass. Every OTHER function in `state.cts` — and every function in every
|
||||
// OTHER file — is still scanned and still flagged.
|
||||
const SEAM_OWNER_EXEMPT_FUNCTIONS = ['writeStateMd', 'readModifyWriteStateMd'];
|
||||
// FUNCTIONS are": a `writeStateMd(`/`syncStateFrontmatter(`/
|
||||
// `applyPostSyncPreservation(` call inside one of these two functions, in
|
||||
// `SEAM_OWNER_FILE` only, is the seam's own internal plumbing, not a bypass.
|
||||
// `writeStateMd` is the `cmdStateSync`/`REGENERATE_STATE` path's own I/O
|
||||
// wrapper calling `syncStateFrontmatter` directly (no preservation, by
|
||||
// design — §8.3's closed exception list). `syncAndPreserveStateMd` (#3469)
|
||||
// is the ONE write-seam composition — `syncStateFrontmatter` then
|
||||
// `applyPostSyncPreservation` — every OTHER caller needing a non-standard
|
||||
// I/O envelope routes through. Every OTHER function in `state.cts` — and
|
||||
// every function in every OTHER file — is still scanned and still flagged;
|
||||
// in particular, `readModifyWriteStateMd` is NOT exempt: after #3469 it no
|
||||
// longer contains a direct `syncStateFrontmatter(`/`applyPostSyncPreservation(`
|
||||
// call at all (it calls `syncAndPreserveStateMd` like everyone else), so if
|
||||
// one reappeared there it would be exactly the re-assembly shape this axis
|
||||
// exists to catch.
|
||||
const SEAM_OWNER_EXEMPT_FUNCTIONS = ['writeStateMd', 'syncAndPreserveStateMd'];
|
||||
|
||||
// Unconditional path-separator normalization (never gated on
|
||||
// `process.platform` — a Windows-authored fork PR must be judged by the
|
||||
@@ -395,20 +407,128 @@ function findUnimplementedPolicies(text, rel) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// The two write-seam functions, matched only as CALLS (`\(` immediately
|
||||
// after, modulo whitespace) — never as bare mentions of the name.
|
||||
const SEAM_CALL_RE = /\b(writeStateMd|syncStateFrontmatter)\s*\(/g;
|
||||
// A line that IS one of the two seam functions' own definitions — skipped
|
||||
// outright, never counted as a call to itself.
|
||||
const SEAM_DEF_LINE_RE = /^\s*(?:export\s+)?(?:async\s+)?function\s+(?:writeStateMd|syncStateFrontmatter)\b/;
|
||||
// AXIS 3 (§8.3(b), closed Phase 2 / #3469): `stateReplaceField(<contentArg>,
|
||||
// <fieldArg>, ...)` on a single line, capturing both argument expressions.
|
||||
// `contentArg` must be a bare identifier (a call expression or property
|
||||
// access as the first argument is not matched — silently out of scope, per
|
||||
// this axis's own narrow-limitation note below) so its assignments can be
|
||||
// tracked; `fieldArg` is everything up to the next comma, trimmed, so its
|
||||
// literal-vs-variable shape can be read off directly.
|
||||
const STATE_REPLACE_FIELD_CALL_RE = /\bstateReplaceField\s*\(\s*([A-Za-z_$][\w$]*)\s*,\s*([^,()]+),/g;
|
||||
|
||||
// True when `arg` (already trimmed) is a fixed string/template literal —
|
||||
// the safe shape, since every literal field name this codebase actually
|
||||
// uses is a Title-Case body label that cannot collide with a lowercase/
|
||||
// snake_case YAML frontmatter key.
|
||||
function isQuotedLiteralArg(arg) {
|
||||
const t = arg.trim();
|
||||
return t.startsWith("'") || t.startsWith('"') || t.startsWith('`');
|
||||
}
|
||||
|
||||
/**
|
||||
* AXIS 2a: every direct `writeStateMd(`/`syncStateFrontmatter(` call in
|
||||
* `text`, outside the two functions' own definitions and (only inside
|
||||
* `SEAM_OWNER_FILE`) outside `SEAM_OWNER_EXEMPT_FUNCTIONS`'s own bodies. No
|
||||
* `reason` on these findings — `applyRatchet` assigns one, since the same
|
||||
* observed call site is a different failure shape depending on whether the
|
||||
* baseline already knows about it.
|
||||
* The nearest assignment to `varName` (`varName = <expr>` or
|
||||
* `const|let|var varName = <expr>`), scanning `lines` BACKWARD from `index`
|
||||
* (inclusive) and stopping at the nearest preceding named-function
|
||||
* declaration (mirrors `enclosingFunction`'s own boundary, so the scan
|
||||
* cannot walk into an unrelated function above the one containing the
|
||||
* call). Returns the assigned expression's trimmed text, or `null` when no
|
||||
* such assignment is found before the boundary — meaning `varName` is the
|
||||
* enclosing function's own untouched parameter.
|
||||
*
|
||||
* Deliberately single-hop: this reports whatever the NEAREST assignment's
|
||||
* right-hand side literally is, and does not itself follow a further alias
|
||||
* (`let body = someOtherVar;` is reported as `"someOtherVar"`, not resolved
|
||||
* further). Every real call site in this file assigns its body variable
|
||||
* directly from `stripFrontmatter(content)` with no intermediate alias
|
||||
* (`updateCore`, `patchCore`, `beginPhaseCore`'s `tryField` helper) — a
|
||||
* future call site that introduces one extra hop of aliasing would evade
|
||||
* this check. A declared, narrow limitation, not a silent one — mirrors
|
||||
* this file's existing precedent (`FIELD_VAR_EQ_LITERAL_RE`'s own
|
||||
* documented scope) of accepting a bounded risk in trade for not chasing
|
||||
* full dataflow, which is exactly what made the Phase 1 approximation
|
||||
* unusable (29 false positives to 1 true positive).
|
||||
*/
|
||||
function nearestPrecedingAssignment(lines, index, varName) {
|
||||
const assignRe = new RegExp(`(?:^|[^.\\w$])(?:const|let|var)?\\s*${escapeRegex(varName)}\\s*=\\s*([^=].*)$`);
|
||||
for (let i = index; i >= 0; i--) {
|
||||
if (FUNCTION_DECL_LINE_RE.test(lines[i])) return null;
|
||||
const m = assignRe.exec(lines[i]);
|
||||
if (m) return m[1].trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* AXIS 3: every `stateReplaceField(` call in `EXECUTOR_FILE` whose field-name
|
||||
* argument is a VARIABLE (not a quoted literal) — the only shape that can
|
||||
* ever rewrite YAML frontmatter, since `stateReplaceField`'s `^field:` line
|
||||
* pattern is case-insensitive and matches any line starting with that name,
|
||||
* literal or not — AND whose content argument was not assigned from
|
||||
* `stripFrontmatter(` at the nearest preceding assignment. A literal
|
||||
* field-name argument is never flagged regardless of stripping: every fixed
|
||||
* string this file's `stateReplaceField` calls use is a Title-Case body
|
||||
* label (`'Phase'`, `'Total Plans in Phase'`, ...) that cannot collide with
|
||||
* a lowercase/snake_case frontmatter key by construction, so checking its
|
||||
* content argument would only add false positives on the ~20 already-safe
|
||||
* `sectionBody`-scoped calls this axis must NOT report (mirrors
|
||||
* `updateCore`'s strip-then-replace shape, and `beginPhaseCore`'s
|
||||
* `stateReplaceField(body, name, value)`, both legitimately unflagged).
|
||||
*/
|
||||
function findUnstrippedContentWrites(rel, text) {
|
||||
const rawLines = text.split('\n');
|
||||
const stripped = stripComments(text);
|
||||
const out = [];
|
||||
for (let i = 0; i < stripped.length; i++) {
|
||||
const line = stripped[i];
|
||||
if (!line.trim()) continue;
|
||||
STATE_REPLACE_FIELD_CALL_RE.lastIndex = 0;
|
||||
let m;
|
||||
while ((m = STATE_REPLACE_FIELD_CALL_RE.exec(line)) !== null) {
|
||||
const contentArg = m[1];
|
||||
const fieldArg = m[2];
|
||||
if (isQuotedLiteralArg(fieldArg)) continue;
|
||||
const assignment = nearestPrecedingAssignment(stripped, i - 1, contentArg);
|
||||
const isStripped = assignment !== null && /^stripFrontmatter\s*\(/.test(assignment);
|
||||
if (isStripped) continue;
|
||||
// `file`/`source` sanitized for the same fork-PR reason as every other
|
||||
// finding in this guard; `contentArg` is captured out of repo source
|
||||
// (an identifier name), attacker-controlled on the same basis.
|
||||
out.push({
|
||||
reason: REASON.UNSTRIPPED_CONTENT_WRITE,
|
||||
axis: 'frontmatter-write',
|
||||
file: sanitizeForReport(rel),
|
||||
line: i + 1,
|
||||
field: sanitizeForReport(contentArg),
|
||||
source: sanitizeForReport(rawLines[i].trim()),
|
||||
});
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// The three write-seam functions, matched only as CALLS (`\(` immediately
|
||||
// after, modulo whitespace) — never as bare mentions of the name.
|
||||
// `applyPostSyncPreservation` (#3469) is included alongside
|
||||
// `writeStateMd`/`syncStateFrontmatter`: after Phase 2, it is ONLY ever
|
||||
// legitimately called from inside `syncAndPreserveStateMd` (the seam
|
||||
// composition), so any OTHER call to it is either a re-assembly of the pair
|
||||
// (Phase 2's Finding 3 shape — a call site invoking both
|
||||
// `syncStateFrontmatter` and `applyPostSyncPreservation` itself instead of
|
||||
// the composition) or a bypass calling it alone; either way it belongs on
|
||||
// this axis.
|
||||
const SEAM_CALL_RE = /\b(writeStateMd|syncStateFrontmatter|applyPostSyncPreservation)\s*\(/g;
|
||||
// A line that IS one of the three seam functions' own definitions — skipped
|
||||
// outright, never counted as a call to itself.
|
||||
const SEAM_DEF_LINE_RE = /^\s*(?:export\s+)?(?:async\s+)?function\s+(?:writeStateMd|syncStateFrontmatter|applyPostSyncPreservation)\b/;
|
||||
|
||||
/**
|
||||
* AXIS 2a: every direct `writeStateMd(`/`syncStateFrontmatter(`/
|
||||
* `applyPostSyncPreservation(` call in `text`, outside the three functions'
|
||||
* own definitions and (only inside `SEAM_OWNER_FILE`) outside
|
||||
* `SEAM_OWNER_EXEMPT_FUNCTIONS`'s own bodies. No `reason` on these
|
||||
* findings — `applyRatchet` assigns one, since the same observed call site
|
||||
* is a different failure shape depending on whether the baseline already
|
||||
* knows about it.
|
||||
*/
|
||||
function findSeamBypasses(rel, text) {
|
||||
const rawLines = text.split('\n');
|
||||
@@ -654,11 +774,13 @@ function applyRatchet(observed, baseline) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run both scan passes (the `src/` tree for Axis 1 + Axis 2a, the prompt
|
||||
* layer for Axis 2b) and split the combined findings by `axis` into
|
||||
* Run both scan passes (the `src/` tree for Axis 1 + Axis 2a + Axis 3, the
|
||||
* prompt layer for Axis 2b) and split the combined findings by `axis` into
|
||||
* `{ policyFindings, seamFindings }`. `policyFindings` are already terminal
|
||||
* (each carries its own `reason`); `seamFindings` are raw observations —
|
||||
* `applyRatchet` is what turns them into (or clears them of) a finding.
|
||||
* (each carries its own `reason`) — this bucket is every axis EXCEPT
|
||||
* `write-seam` (Axis 2), which alone is ratcheted; `seamFindings` are raw
|
||||
* write-seam observations — `applyRatchet` is what turns them into (or
|
||||
* clears them of) a finding.
|
||||
*/
|
||||
function collect() {
|
||||
const srcFindings = scanTree({
|
||||
@@ -671,6 +793,7 @@ function collect() {
|
||||
if (relPosix === EXECUTOR_FILE) {
|
||||
found.push(...findPolicyDispatchDrift(relPosix, text));
|
||||
found.push(...findUnimplementedPolicies(text, relPosix));
|
||||
found.push(...findUnstrippedContentWrites(relPosix, text));
|
||||
}
|
||||
found.push(...findSeamBypasses(relPosix, text));
|
||||
return found;
|
||||
@@ -688,7 +811,7 @@ function collect() {
|
||||
|
||||
const all = [...srcFindings, ...promptFindings];
|
||||
return {
|
||||
policyFindings: all.filter((f) => f.axis === 'policy-dispatch'),
|
||||
policyFindings: all.filter((f) => f.axis !== 'write-seam'),
|
||||
seamFindings: all.filter((f) => f.axis === 'write-seam'),
|
||||
};
|
||||
}
|
||||
@@ -745,18 +868,23 @@ function buildBaselineEntries(seamFindings, existingEntries) {
|
||||
}
|
||||
|
||||
const BASELINE_COMMENT =
|
||||
'ADR-3408 Decision 5 write-seam ratchet baseline (issue #3468, Phase 1). Every entry here is a ' +
|
||||
'`writeStateMd(`/`syncStateFrontmatter(` bypass this guard found by a whole-repo scan (Decision ' +
|
||||
'4(a)) — it is ACKNOWLEDGED, not endorsed: acknowledgment is in writing (this file), with the ' +
|
||||
'issue owning its removal recorded in the entry\'s "owner" field. This baseline is SHRINK-ONLY — ' +
|
||||
'an entry that stops firing goes STALE and fails the plain run until `--baseline` is re-run to ' +
|
||||
'drop it (ADR-3180 Decision 4(e)\'s "the baseline may only shrink", adopted verbatim by ADR-3408). ' +
|
||||
'Phase 2 (#3469) removes the `cmdPhaseComplete` and `patchCore` entries when it lands the single ' +
|
||||
'write seam. Phase 4 (#3471) drives this baseline to empty and deletes this file. ' +
|
||||
'`REGENERATE_STATE` (`src/health-diagnostic.cts`) is a SANCTIONED PERMANENT exception, not debt — ' +
|
||||
'it is `/gsd-health --repair`\'s factory reset, which rebuilds STATE.md from scratch, so ' +
|
||||
'preservation would restore exactly the values it was invoked to discard; do not "consolidate" ' +
|
||||
'its entry away.';
|
||||
'ADR-3408 Decision 5 write-seam ratchet baseline (issue #3468, Phase 1; Phase 2 / #3469 lands the ' +
|
||||
'single write seam and Amendment 2). Every entry here is a `writeStateMd(`/`syncStateFrontmatter(`/' +
|
||||
'`applyPostSyncPreservation(` bypass this guard found by a whole-repo scan (Decision 4(a)) — it is ' +
|
||||
'ACKNOWLEDGED, not endorsed: acknowledgment is in writing (this file), with the issue owning its ' +
|
||||
'removal recorded in the entry\'s "owner" field. This baseline is SHRINK-ONLY — an entry that stops ' +
|
||||
'firing goes STALE and fails the plain run until `--baseline` is re-run to drop it (ADR-3180 ' +
|
||||
'Decision 4(e)\'s "the baseline may only shrink", adopted verbatim by ADR-3408). Phase 2 (#3469) ' +
|
||||
'removed the `cmdPhaseComplete` (`src/phase.cts`) and `cmdMilestoneComplete` (`src/milestone.cts`) ' +
|
||||
'entries by routing both through the single write-seam composition (`syncAndPreserveStateMd`, ' +
|
||||
'`src/state.cts`). ADR-3408 Amendment 2: "0 bypasses" was never this baseline\'s target — TWO ' +
|
||||
'entries are SANCTIONED PERMANENT, not debt, and Phase 4 (#3471) does NOT drive this file to empty: ' +
|
||||
'`cmdStateSync` (`src/state.cts`) exists precisely to let the body win (#905 — `state sync` ' +
|
||||
're-derives frontmatter FROM the body), so routing it through preservation would invert the command ' +
|
||||
'rather than fix a bug; `REGENERATE_STATE` (`src/health-diagnostic.cts`) is `/gsd-health --repair`\'s ' +
|
||||
'factory reset, which rebuilds STATE.md from scratch, so preservation would restore exactly the ' +
|
||||
'values it was invoked to discard. Neither entry may be "consolidated" away — a guard reporting them ' +
|
||||
'is reporting correctly, and a change that removes one is a regression, not progress.';
|
||||
|
||||
function writeBaseline(seamFindings) {
|
||||
const priorBaseline = loadBaseline();
|
||||
@@ -902,6 +1030,9 @@ module.exports = {
|
||||
readPolicyUnion,
|
||||
findPolicyDispatchDrift,
|
||||
findUnimplementedPolicies,
|
||||
findUnstrippedContentWrites,
|
||||
isQuotedLiteralArg,
|
||||
nearestPrecedingAssignment,
|
||||
findSeamBypasses,
|
||||
findPromptSeamUses,
|
||||
isInsideCodeSpan,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"_comment": "ADR-3408 Decision 5 write-seam ratchet baseline (issue #3468, Phase 1). Every entry here is a `writeStateMd(`/`syncStateFrontmatter(` bypass this guard found by a whole-repo scan (Decision 4(a)) — it is ACKNOWLEDGED, not endorsed: acknowledgment is in writing (this file), with the issue owning its removal recorded in the entry's \"owner\" field. This baseline is SHRINK-ONLY — an entry that stops firing goes STALE and fails the plain run until `--baseline` is re-run to drop it (ADR-3180 Decision 4(e)'s \"the baseline may only shrink\", adopted verbatim by ADR-3408). Phase 2 (#3469) removes the `cmdPhaseComplete` and `patchCore` entries when it lands the single write seam. Phase 4 (#3471) drives this baseline to empty and deletes this file. `REGENERATE_STATE` (`src/health-diagnostic.cts`) is a SANCTIONED PERMANENT exception, not debt — it is `/gsd-health --repair`'s factory reset, which rebuilds STATE.md from scratch, so preservation would restore exactly the values it was invoked to discard; do not \"consolidate\" its entry away.",
|
||||
"_comment": "ADR-3408 Decision 5 write-seam ratchet baseline (issue #3468, Phase 1; Phase 2 / #3469 lands the single write seam and Amendment 2). Every entry here is a `writeStateMd(`/`syncStateFrontmatter(`/`applyPostSyncPreservation(` bypass this guard found by a whole-repo scan (Decision 4(a)) — it is ACKNOWLEDGED, not endorsed: acknowledgment is in writing (this file), with the issue owning its removal recorded in the entry's \"owner\" field. This baseline is SHRINK-ONLY — an entry that stops firing goes STALE and fails the plain run until `--baseline` is re-run to drop it (ADR-3180 Decision 4(e)'s \"the baseline may only shrink\", adopted verbatim by ADR-3408). Phase 2 (#3469) removed the `cmdPhaseComplete` (`src/phase.cts`) and `cmdMilestoneComplete` (`src/milestone.cts`) entries by routing both through the single write-seam composition (`syncAndPreserveStateMd`, `src/state.cts`). ADR-3408 Amendment 2: \"0 bypasses\" was never this baseline's target — TWO entries are SANCTIONED PERMANENT, not debt, and Phase 4 (#3471) does NOT drive this file to empty: `cmdStateSync` (`src/state.cts`) exists precisely to let the body win (#905 — `state sync` re-derives frontmatter FROM the body), so routing it through preservation would invert the command rather than fix a bug; `REGENERATE_STATE` (`src/health-diagnostic.cts`) is `/gsd-health --repair`'s factory reset, which rebuilds STATE.md from scratch, so preservation would restore exactly the values it was invoked to discard. Neither entry may be \"consolidated\" away — a guard reporting them is reporting correctly, and a change that removes one is a regression, not progress.",
|
||||
"entries": [
|
||||
{
|
||||
"file": "src/health-diagnostic.cts",
|
||||
@@ -8,26 +8,12 @@
|
||||
"count": 1,
|
||||
"owner": "sanctioned-permanent"
|
||||
},
|
||||
{
|
||||
"file": "src/milestone.cts",
|
||||
"source": "writeStateMd(statePath, result.content, cwd);",
|
||||
"symbol": "writeStateMd",
|
||||
"count": 1,
|
||||
"owner": "#3471"
|
||||
},
|
||||
{
|
||||
"file": "src/phase.cts",
|
||||
"source": "const synced = syncStateFrontmatter(stateContent, cwd, authoritativeFm);",
|
||||
"symbol": "syncStateFrontmatter",
|
||||
"count": 1,
|
||||
"owner": "#3469"
|
||||
},
|
||||
{
|
||||
"file": "src/state.cts",
|
||||
"source": "writeStateMd(statePath, modified, cwd);",
|
||||
"symbol": "writeStateMd",
|
||||
"count": 1,
|
||||
"owner": "#3471"
|
||||
"owner": "sanctioned-permanent"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user