@deftai/directive-core 0.106.0 → 0.108.0
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.
- package/dist/architecture/sor-preflight.js +2 -0
- package/dist/check/cached-orchestrator.js +28 -3
- package/dist/check/gate-lists.js +2 -0
- package/dist/check/named-cause.js +18 -3
- package/dist/codebase/provider.js +7 -1
- package/dist/delivery-attempt/handoff.js +1 -1
- package/dist/design-critique/citation-grammar.d.ts +53 -0
- package/dist/design-critique/citation-grammar.js +303 -0
- package/dist/design-critique/completed-arc-record.d.ts +62 -0
- package/dist/design-critique/completed-arc-record.js +232 -0
- package/dist/design-critique/exclusive-chip.d.ts +33 -0
- package/dist/design-critique/exclusive-chip.js +68 -0
- package/dist/design-critique/parent-audit.d.ts +59 -0
- package/dist/design-critique/parent-audit.js +121 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +35 -0
- package/dist/freshness/bind.d.ts +32 -3
- package/dist/freshness/bind.js +58 -12
- package/dist/hooks/classify/host-session-identity.d.ts +76 -0
- package/dist/hooks/classify/host-session-identity.js +470 -0
- package/dist/hooks/classify/index.d.ts +3 -2
- package/dist/hooks/classify/index.js +3 -2
- package/dist/hooks/classify/paths.d.ts +7 -0
- package/dist/hooks/classify/paths.js +35 -0
- package/dist/hooks/classify/stdin.d.ts +5 -0
- package/dist/hooks/classify/stdin.js +27 -0
- package/dist/hooks/dest-form.d.ts +46 -0
- package/dist/hooks/dest-form.js +575 -0
- package/dist/hooks/dispatcher.d.ts +69 -2
- package/dist/hooks/dispatcher.js +609 -45
- package/dist/hooks/fixtures/cases.d.ts +16 -2
- package/dist/hooks/fixtures/cases.js +175 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/scope.js +3 -1
- package/dist/init-deposit/agent-hooks.d.ts +2 -1
- package/dist/init-deposit/agent-hooks.js +8 -1
- package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
- package/dist/init-deposit/hook-runtime-travel.js +269 -0
- package/dist/intake/clause-derivation.js +9 -3
- package/dist/intake/github-auth-modes-cli.js +14 -2
- package/dist/intake/github-auth-modes.d.ts +54 -9
- package/dist/intake/github-auth-modes.js +326 -123
- package/dist/intake/issue-ingest.js +62 -2
- package/dist/intake/platform-capabilities.d.ts +9 -2
- package/dist/intake/platform-capabilities.js +38 -9
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
- package/dist/lifecycle/completed-write-guard.d.ts +48 -0
- package/dist/lifecycle/completed-write-guard.js +373 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/capture.d.ts +16 -0
- package/dist/literal-acceptance/capture.js +75 -7
- package/dist/literal-acceptance/evaluate.js +25 -3
- package/dist/literal-acceptance/index.d.ts +2 -2
- package/dist/literal-acceptance/index.js +2 -2
- package/dist/literal-acceptance/run.js +9 -3
- package/dist/literal-acceptance/types.d.ts +6 -0
- package/dist/literal-acceptance/types.js +6 -0
- package/dist/orphan-active/evaluate.d.ts +25 -0
- package/dist/orphan-active/evaluate.js +193 -128
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/orphan-active/issue-state.d.ts +121 -0
- package/dist/orphan-active/issue-state.js +278 -0
- package/dist/platform/cursor-managed-runtime.d.ts +95 -0
- package/dist/platform/cursor-managed-runtime.js +241 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/platform/platform-capabilities.d.ts +23 -1
- package/dist/platform/platform-capabilities.js +55 -15
- package/dist/policy/ceremony-dial.js +5 -9
- package/dist/policy/host-hooks.js +4 -9
- package/dist/policy/org-force-on-migration.js +5 -10
- package/dist/policy/plan-extensions.d.ts +14 -2
- package/dist/policy/plan-extensions.js +24 -3
- package/dist/policy/product-signal.js +5 -10
- package/dist/policy/require-human-merge.js +5 -9
- package/dist/policy/resolve.js +77 -16
- package/dist/policy/runtime-authority.d.ts +18 -0
- package/dist/policy/runtime-authority.js +11 -0
- package/dist/policy/value-feedback.js +9 -18
- package/dist/policy/write-fence.js +3 -0
- package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
- package/dist/pr-closeout-attestable/evaluate.js +306 -0
- package/dist/pr-closeout-attestable/index.d.ts +2 -0
- package/dist/pr-closeout-attestable/index.js +2 -0
- package/dist/pr-protected-issues/gh.d.ts +6 -2
- package/dist/pr-protected-issues/gh.js +7 -3
- package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
- package/dist/pr-wait-mergeable/cascade.js +21 -1
- package/dist/pr-wait-mergeable/types.d.ts +2 -0
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
- package/dist/pr-wait-mergeable/wrappers.js +11 -0
- package/dist/preflight/evaluate.d.ts +15 -0
- package/dist/preflight/evaluate.js +29 -3
- package/dist/preflight-cache/evaluate.js +9 -3
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
- package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
- package/dist/product-first-done-gate/evaluate.js +53 -12
- package/dist/release/cli-drift-report.d.ts +68 -0
- package/dist/release/cli-drift-report.js +189 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/issue-state-fetch.d.ts +5 -3
- package/dist/release/issue-state-fetch.js +86 -21
- package/dist/release/native-steps.js +1 -0
- package/dist/release/pipeline.js +19 -0
- package/dist/release/types.d.ts +14 -0
- package/dist/render/constants.d.ts +4 -0
- package/dist/render/constants.js +11 -8
- package/dist/render/export-spec.js +31 -5
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -1
- package/dist/render/prd-render.d.ts +4 -1
- package/dist/render/prd-render.js +82 -23
- package/dist/render/project-render.js +14 -7
- package/dist/render/scope-outlook.d.ts +2 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.js +2 -2
- package/dist/resolution/package-manager.d.ts +33 -4
- package/dist/resolution/package-manager.js +210 -9
- package/dist/review-monitor/constants.d.ts +7 -0
- package/dist/review-monitor/constants.js +7 -0
- package/dist/review-monitor/github-lease.d.ts +98 -1
- package/dist/review-monitor/github-lease.js +182 -1
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +103 -2
- package/dist/scm/call.js +12 -3
- package/dist/scm/design-critique-chip.d.ts +38 -0
- package/dist/scm/design-critique-chip.js +187 -0
- package/dist/scm/gh-rest.d.ts +9 -1
- package/dist/scm/gh-rest.js +103 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +3 -0
- package/dist/scm/main.js +15 -1
- package/dist/scm/readiness-cli.d.ts +2 -0
- package/dist/scm/readiness-cli.js +42 -1
- package/dist/scm/readiness.d.ts +4 -1
- package/dist/scm/readiness.js +35 -5
- package/dist/scope/decompose.js +2 -1
- package/dist/scope/delivery-evidence.d.ts +27 -1
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/lifecycle-write.d.ts +24 -0
- package/dist/scope/lifecycle-write.js +64 -0
- package/dist/scope/main.js +3 -1
- package/dist/scope/project-definition-sync.js +14 -7
- package/dist/scope/transition.js +90 -11
- package/dist/scope-provenance/evaluate.js +5 -1
- package/dist/session/git.d.ts +42 -0
- package/dist/session/git.js +219 -3
- package/dist/session/occupancy.d.ts +239 -12
- package/dist/session/occupancy.js +820 -27
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +5 -0
- package/dist/session/session-ready.d.ts +5 -1
- package/dist/session/session-ready.js +98 -8
- package/dist/session/session-start.d.ts +17 -1
- package/dist/session/session-start.js +49 -17
- package/dist/session/toolchain-preflight.d.ts +13 -3
- package/dist/session/toolchain-preflight.js +93 -18
- package/dist/session/verify-session-ritual.d.ts +33 -3
- package/dist/session/verify-session-ritual.js +89 -19
- package/dist/slice/constants.d.ts +1 -1
- package/dist/spec-authority/constants.d.ts +10 -2
- package/dist/spec-authority/constants.js +53 -8
- package/dist/spec-authority/resolver.d.ts +3 -0
- package/dist/spec-authority/resolver.js +55 -7
- package/dist/subprocess/max-buffer.d.ts +14 -0
- package/dist/subprocess/max-buffer.js +15 -0
- package/dist/swarm/complete-cohort.d.ts +2 -0
- package/dist/swarm/complete-cohort.js +21 -13
- package/dist/swarm/index.d.ts +1 -0
- package/dist/swarm/index.js +1 -0
- package/dist/swarm/launch-cli.js +53 -33
- package/dist/swarm/launch.d.ts +77 -0
- package/dist/swarm/launch.js +248 -38
- package/dist/swarm/pre-dispatch-cli.js +2 -1
- package/dist/swarm/pre-dispatch.js +10 -1
- package/dist/swarm/subagent-status-dir.d.ts +27 -0
- package/dist/swarm/subagent-status-dir.js +42 -0
- package/dist/swarm/worktrees.d.ts +17 -3
- package/dist/swarm/worktrees.js +48 -4
- package/dist/triage/actions/index.d.ts +4 -0
- package/dist/triage/actions/index.js +6 -0
- package/dist/triage/evaluate/evaluate.d.ts +11 -0
- package/dist/triage/evaluate/evaluate.js +169 -0
- package/dist/triage/evaluate/github.d.ts +16 -0
- package/dist/triage/evaluate/github.js +153 -0
- package/dist/triage/evaluate/index.d.ts +9 -0
- package/dist/triage/evaluate/index.js +8 -0
- package/dist/triage/evaluate/paths.d.ts +7 -0
- package/dist/triage/evaluate/paths.js +25 -0
- package/dist/triage/evaluate/sink.d.ts +5 -0
- package/dist/triage/evaluate/sink.js +52 -0
- package/dist/triage/evaluate/types.d.ts +93 -0
- package/dist/triage/evaluate/types.js +13 -0
- package/dist/triage/evaluate/validity.d.ts +8 -0
- package/dist/triage/evaluate/validity.js +87 -0
- package/dist/triage/evaluate/value.d.ts +12 -0
- package/dist/triage/evaluate/value.js +33 -0
- package/dist/triage/evaluate/wip-census.d.ts +5 -0
- package/dist/triage/evaluate/wip-census.js +39 -0
- package/dist/triage/evaluate/worktrees.d.ts +8 -0
- package/dist/triage/evaluate/worktrees.js +68 -0
- package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
- package/dist/triage/evaluate/xbrief-refs.js +60 -0
- package/dist/triage/help/registry-data.d.ts +21 -7
- package/dist/triage/help/registry-data.js +53 -5
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/scope/mutations-core.d.ts +2 -6
- package/dist/triage/scope/mutations-core.js +23 -55
- package/dist/triage/scope-drift/add-ignore.js +5 -39
- package/dist/triage/subscribe/index.d.ts +2 -6
- package/dist/triage/subscribe/index.js +15 -65
- package/dist/triage/welcome/writers.js +15 -74
- package/dist/value/feedback-file.d.ts +17 -2
- package/dist/value/feedback-file.js +80 -6
- package/dist/vbrief-build/index.d.ts +2 -1
- package/dist/vbrief-build/index.js +2 -1
- package/dist/vbrief-build/parity-scenarios.js +5 -5
- package/dist/vbrief-build/project-definition-io.d.ts +63 -3
- package/dist/vbrief-build/project-definition-io.js +357 -31
- package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
- package/dist/vbrief-build/project-definition-mutation.js +43 -0
- package/dist/vbrief-reconcile/labels.d.ts +1 -0
- package/dist/vbrief-reconcile/labels.js +30 -0
- package/dist/vbrief-validate/conformance.d.ts +1 -0
- package/dist/vbrief-validate/conformance.js +85 -8
- package/dist/vbrief-validate/main.js +2 -0
- package/dist/vbrief-validate/precutover.js +5 -10
- package/dist/vbrief-validation/story-quality.d.ts +7 -0
- package/dist/vbrief-validation/story-quality.js +8 -1
- package/dist/verify-ac/clauses.d.ts +62 -2
- package/dist/verify-ac/clauses.js +183 -111
- package/dist/verify-ac/index.d.ts +1 -1
- package/dist/verify-ac/index.js +1 -1
- package/dist/verify-env/node-runtime.d.ts +8 -4
- package/dist/verify-env/node-runtime.js +9 -6
- package/dist/verify-env/toolchain-check.d.ts +35 -3
- package/dist/verify-env/toolchain-check.js +155 -36
- package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
- package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
- package/dist/xbrief-migrate/agents-header.js +69 -7
- package/package.json +7 -3
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completed-arc record for design-critique ingest (#3806).
|
|
3
|
+
*
|
|
4
|
+
* Catalog chips are list-visible convenience, not clearance. Ingest waits on
|
|
5
|
+
* `design-critique: synthesis accepted, because ...` citing an accepted
|
|
6
|
+
* successor lean (and the verified-claims table when that comment exists).
|
|
7
|
+
*
|
|
8
|
+
* Citations are parsed once, by `scanCitations` (#3831). The accepted forms and
|
|
9
|
+
* the refused positions are published in `content/contracts/design-critique.md`
|
|
10
|
+
* `## Citation grammar`. Clearance is set membership against the latest
|
|
11
|
+
* successor lean, not position in the body, so citing the superseded lean --
|
|
12
|
+
* which `## Successor lean` requires -- cannot block.
|
|
13
|
+
*/
|
|
14
|
+
import { ACCEPTED_CITATION_FORMS, scanCitations, } from "./citation-grammar.js";
|
|
15
|
+
import { DESIGN_CRITIQUE_CATALOG_CHIPS } from "./exclusive-chip.js";
|
|
16
|
+
const SYNTHESIS_SHAPE_RE = /(?:^|\n)\s*design-critique:\s*synthesis accepted,\s*because\b/i;
|
|
17
|
+
const LEAN_HEADING_RE = /(?:^|\n)\s*\*{0,2}Lean:\*{0,2}/;
|
|
18
|
+
const TABLE_HEADING_RE = /(?:^|\n)\s*##\s+Verified-claims table\b/;
|
|
19
|
+
/** How many ids a block detail lists before it truncates. */
|
|
20
|
+
const DETAIL_ID_LIMIT = 5;
|
|
21
|
+
const CATALOG = new Set(DESIGN_CRITIQUE_CATALOG_CHIPS);
|
|
22
|
+
export class DesignCritiqueIngestBlockedError extends Error {
|
|
23
|
+
issueNumber;
|
|
24
|
+
reason;
|
|
25
|
+
constructor(issueNumber, reason, detail) {
|
|
26
|
+
super(`issue:ingest refused #${issueNumber}: design-critique ${reason} (${detail}) -- nothing written.`);
|
|
27
|
+
this.name = "DesignCritiqueIngestBlockedError";
|
|
28
|
+
this.issueNumber = issueNumber;
|
|
29
|
+
this.reason = reason;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function isSynthesisAcceptedShape(body) {
|
|
33
|
+
return SYNTHESIS_SHAPE_RE.test(body);
|
|
34
|
+
}
|
|
35
|
+
export function isSuccessorLeanBody(body) {
|
|
36
|
+
return LEAN_HEADING_RE.test(body);
|
|
37
|
+
}
|
|
38
|
+
export function isVerifiedClaimsTableBody(body) {
|
|
39
|
+
return TABLE_HEADING_RE.test(body);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Ids cited by one comment body, in document order. Thin projection of the
|
|
43
|
+
* shared parser -- the grammar itself lives in `citation-grammar.ts`.
|
|
44
|
+
*/
|
|
45
|
+
export function extractCitedCommentIds(body) {
|
|
46
|
+
const ids = [];
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
for (const citation of scanCitations(body).citations) {
|
|
49
|
+
if (seen.has(citation.id))
|
|
50
|
+
continue;
|
|
51
|
+
seen.add(citation.id);
|
|
52
|
+
ids.push(citation.id);
|
|
53
|
+
}
|
|
54
|
+
return ids;
|
|
55
|
+
}
|
|
56
|
+
export function hasDesignCritiqueCatalogChip(labels) {
|
|
57
|
+
return labels.some((name) => CATALOG.has(name));
|
|
58
|
+
}
|
|
59
|
+
const CRITIC_ROLE_RE = /(?:^|\n)\s*role:\s*critic\b/i;
|
|
60
|
+
const MECHANISM_SHAPED_FIELD_RE = /(?:^|\n)\s*mechanism-shaped:\s*true\b/i;
|
|
61
|
+
const PANEL_DEPOSIT_RE = /(?:^|\n)\s*panel-deposit\b/i;
|
|
62
|
+
const PARENT_ROLE_RE = /(?:^|\n)\s*role:\s*parent\b/i;
|
|
63
|
+
const SIBLINGS_FIELD_RE = /(?:^|\n)\s*siblings:\s*\d+/i;
|
|
64
|
+
const INPUT_CEILING_FIELD_RE = /(?:^|\n)\s*input-ceiling:\s*\d+/i;
|
|
65
|
+
export function isPanelDepositBody(body) {
|
|
66
|
+
if (PANEL_DEPOSIT_RE.test(body))
|
|
67
|
+
return true;
|
|
68
|
+
return (PARENT_ROLE_RE.test(body) && SIBLINGS_FIELD_RE.test(body) && INPUT_CEILING_FIELD_RE.test(body));
|
|
69
|
+
}
|
|
70
|
+
export function isInFlightCritiqueThread(comments) {
|
|
71
|
+
return comments.some((comment) => isSuccessorLeanBody(comment.body) ||
|
|
72
|
+
CRITIC_ROLE_RE.test(comment.body) ||
|
|
73
|
+
MECHANISM_SHAPED_FIELD_RE.test(comment.body) ||
|
|
74
|
+
isPanelDepositBody(comment.body));
|
|
75
|
+
}
|
|
76
|
+
function byId(comments) {
|
|
77
|
+
const map = new Map();
|
|
78
|
+
for (const comment of comments) {
|
|
79
|
+
map.set(comment.id, comment);
|
|
80
|
+
}
|
|
81
|
+
return map;
|
|
82
|
+
}
|
|
83
|
+
function latestSuccessorLean(comments) {
|
|
84
|
+
let latest;
|
|
85
|
+
for (const comment of comments) {
|
|
86
|
+
if (!isSuccessorLeanBody(comment.body))
|
|
87
|
+
continue;
|
|
88
|
+
if (latest === undefined || comment.id > latest.id)
|
|
89
|
+
latest = comment;
|
|
90
|
+
}
|
|
91
|
+
return latest;
|
|
92
|
+
}
|
|
93
|
+
function renderIds(ids) {
|
|
94
|
+
const shown = ids.slice(0, DETAIL_ID_LIMIT).join(", ");
|
|
95
|
+
return ids.length > DETAIL_ID_LIMIT
|
|
96
|
+
? `${shown}, and ${ids.length - DETAIL_ID_LIMIT} more`
|
|
97
|
+
: shown;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Report what was scanned and what was found. A guess at the cause sends the
|
|
101
|
+
* operator back to re-post the same body and reproduce the block (#3831).
|
|
102
|
+
*/
|
|
103
|
+
function loneShapeDetail(scan) {
|
|
104
|
+
const parts = [
|
|
105
|
+
"synthesis-accepted sentence shape is present but cites no accepted successor lean",
|
|
106
|
+
];
|
|
107
|
+
if (scan.idShapedRuns.length === 0) {
|
|
108
|
+
parts.push("no 8-or-more digit id appears in the body");
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
parts.push(`${scan.idShapedRuns.length} 8-or-more digit id(s) appear in the body: ` +
|
|
112
|
+
renderIds(scan.idShapedRuns));
|
|
113
|
+
}
|
|
114
|
+
if (scan.rejected.length > 0) {
|
|
115
|
+
const classes = [...new Set(scan.rejected.map((row) => row.reason))].sort().join(", ");
|
|
116
|
+
parts.push(`${scan.rejected.length} keyword-anchored occurrence(s) refused by position (${classes})`);
|
|
117
|
+
}
|
|
118
|
+
parts.push(`accepted forms: ${ACCEPTED_CITATION_FORMS.join(" | ")}`);
|
|
119
|
+
return parts.join("; ");
|
|
120
|
+
}
|
|
121
|
+
function resolveCitedLean(citations, byCommentId, latestLean) {
|
|
122
|
+
if (latestLean !== undefined && citations.some((row) => row.id === latestLean.id)) {
|
|
123
|
+
return latestLean;
|
|
124
|
+
}
|
|
125
|
+
return citations
|
|
126
|
+
.map((row) => byCommentId.get(row.id))
|
|
127
|
+
.find((row) => row !== undefined && isSuccessorLeanBody(row.body));
|
|
128
|
+
}
|
|
129
|
+
function verdictForSynthesis(comment, comments) {
|
|
130
|
+
const scan = scanCitations(comment.body);
|
|
131
|
+
const citations = scan.citations;
|
|
132
|
+
if (citations.length === 0) {
|
|
133
|
+
return {
|
|
134
|
+
status: "blocked",
|
|
135
|
+
reason: "lone-shape",
|
|
136
|
+
detail: loneShapeDetail(scan),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const byCommentId = byId(comments);
|
|
140
|
+
const citedLean = resolveCitedLean(citations, byCommentId, latestSuccessorLean(comments));
|
|
141
|
+
if (citedLean === undefined) {
|
|
142
|
+
return {
|
|
143
|
+
status: "blocked",
|
|
144
|
+
reason: "cite-not-lean",
|
|
145
|
+
detail: "no cited id is a successor lean on this thread; cited: " +
|
|
146
|
+
renderIds(citations.map((row) => row.id)),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const citedTable = citations
|
|
150
|
+
.map((row) => byCommentId.get(row.id))
|
|
151
|
+
.find((row) => row !== undefined && isVerifiedClaimsTableBody(row.body));
|
|
152
|
+
if (citedTable === undefined) {
|
|
153
|
+
const claimed = citations.filter((row) => row.kind === "table");
|
|
154
|
+
if (claimed.length > 0) {
|
|
155
|
+
return {
|
|
156
|
+
status: "blocked",
|
|
157
|
+
reason: "missing-table-cite",
|
|
158
|
+
detail: "synthesis cites a verified-claims table id that is not a table on this thread: " +
|
|
159
|
+
renderIds(claimed.map((row) => row.id)),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
status: "complete",
|
|
165
|
+
synthesisCommentId: comment.id,
|
|
166
|
+
citedLeanId: citedLean.id,
|
|
167
|
+
citedTableId: citedTable?.id ?? null,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Ingest clearance from thread structure. Labels and author identity are not
|
|
172
|
+
* predicates. A lone synthesis-accepted sentence shape is not the record.
|
|
173
|
+
* Clearance cites the latest successor lean; an older complete record does not
|
|
174
|
+
* clear a later recut. A panel-deposit is in-flight even before critic posts.
|
|
175
|
+
*/
|
|
176
|
+
export function evaluateCompletedArcRecord(input) {
|
|
177
|
+
const comments = input.comments;
|
|
178
|
+
const labels = input.labels ?? [];
|
|
179
|
+
const synthesis = comments.filter((c) => isSynthesisAcceptedShape(c.body));
|
|
180
|
+
const completeRecords = synthesis
|
|
181
|
+
.map((comment) => verdictForSynthesis(comment, comments))
|
|
182
|
+
.filter((verdict) => {
|
|
183
|
+
return verdict.status === "complete";
|
|
184
|
+
});
|
|
185
|
+
if (completeRecords.length > 0) {
|
|
186
|
+
const latestLean = latestSuccessorLean(comments);
|
|
187
|
+
const matching = latestLean === undefined
|
|
188
|
+
? completeRecords
|
|
189
|
+
: completeRecords.filter((record) => record.citedLeanId === latestLean.id);
|
|
190
|
+
if (matching.length > 0) {
|
|
191
|
+
return matching.reduce((a, b) => (a.synthesisCommentId >= b.synthesisCommentId ? a : b));
|
|
192
|
+
}
|
|
193
|
+
const latestCompleteId = completeRecords.reduce((max, record) => Math.max(max, record.synthesisCommentId), 0);
|
|
194
|
+
const laterSynthesis = synthesis.filter((comment) => comment.id > latestCompleteId);
|
|
195
|
+
if (laterSynthesis.length > 0) {
|
|
196
|
+
const latest = laterSynthesis.reduce((a, b) => (a.id >= b.id ? a : b));
|
|
197
|
+
return verdictForSynthesis(latest, comments);
|
|
198
|
+
}
|
|
199
|
+
const citedLeanIds = completeRecords.map((record) => record.citedLeanId);
|
|
200
|
+
const latestLeanId = latestLean === undefined ? "unknown" : String(latestLean.id);
|
|
201
|
+
return {
|
|
202
|
+
status: "blocked",
|
|
203
|
+
reason: "missing-record",
|
|
204
|
+
detail: `no completed-arc record cites the latest successor lean ${latestLeanId} on this thread; ` +
|
|
205
|
+
`existing record(s) cite lean ${renderIds(citedLeanIds)}; ` +
|
|
206
|
+
`ingest waits on a record citing lean ${latestLeanId}`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (synthesis.length > 0) {
|
|
210
|
+
const latest = synthesis.reduce((a, b) => (a.id >= b.id ? a : b));
|
|
211
|
+
return verdictForSynthesis(latest, comments);
|
|
212
|
+
}
|
|
213
|
+
const inArc = hasDesignCritiqueCatalogChip(labels) || isInFlightCritiqueThread(comments);
|
|
214
|
+
if (!inArc) {
|
|
215
|
+
return { status: "not-in-arc" };
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
status: "blocked",
|
|
219
|
+
reason: "missing-record",
|
|
220
|
+
detail: "design-critique is in flight but the completed-arc record is missing: " +
|
|
221
|
+
"`design-critique: synthesis accepted, because ...` citing the accepted successor lean; " +
|
|
222
|
+
`accepted forms: ${ACCEPTED_CITATION_FORMS.join(" | ")}`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
export function assertCompletedArcAllowsIngest(input) {
|
|
226
|
+
const verdict = evaluateCompletedArcRecord(input);
|
|
227
|
+
if (verdict.status === "blocked") {
|
|
228
|
+
throw new DesignCritiqueIngestBlockedError(input.issueNumber, verdict.reason, verdict.detail);
|
|
229
|
+
}
|
|
230
|
+
return verdict;
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=completed-arc-record.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { LabelClient } from "../vbrief-reconcile/types.js";
|
|
2
|
+
/** Closed catalog. Last chip wins. Not an open `design-critique:*` glob. */
|
|
3
|
+
export declare const DESIGN_CRITIQUE_CATALOG_CHIPS: readonly ["design-critique:mechanism-shaped", "design-critique:triage-ready"];
|
|
4
|
+
export type DesignCritiqueCatalogChip = (typeof DESIGN_CRITIQUE_CATALOG_CHIPS)[number];
|
|
5
|
+
export declare function isDesignCritiqueCatalogChip(name: string): name is DesignCritiqueCatalogChip;
|
|
6
|
+
/**
|
|
7
|
+
* Remaining-set replace: GET current, drop the other catalog names, keep other
|
|
8
|
+
* facets, then PUT/PATCH this list. One write. No DELETE-then-POST window.
|
|
9
|
+
*/
|
|
10
|
+
export declare function remainingSetAfterDesignCritiqueChip(current: readonly string[], nextChip: string): string[];
|
|
11
|
+
/**
|
|
12
|
+
* Fold exclusive catalog-chip replace into a LabelClient.apply add/remove
|
|
13
|
+
* mutation. Production writer: ScmLabelClient.apply.
|
|
14
|
+
*/
|
|
15
|
+
export declare function mergeDesignCritiqueExclusiveIntoApply(current: readonly string[], add: readonly string[], remove: readonly string[]): {
|
|
16
|
+
add: string[];
|
|
17
|
+
remove: string[];
|
|
18
|
+
};
|
|
19
|
+
/** Delta for LabelClient.apply (one add+remove mutation). */
|
|
20
|
+
export declare function designCritiqueChipApplyDelta(current: readonly string[], nextChip: string): {
|
|
21
|
+
add: string[];
|
|
22
|
+
remove: string[];
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Exclusive catalog-chip write via LabelClient.apply. GET current, then one
|
|
26
|
+
* apply(add, remove). Does not PUT a naive full wipe.
|
|
27
|
+
*/
|
|
28
|
+
export declare function applyDesignCritiqueCatalogChip(client: LabelClient, repo: string, issueNumber: number, nextChip: string): {
|
|
29
|
+
remaining: string[];
|
|
30
|
+
add: readonly string[];
|
|
31
|
+
remove: readonly string[];
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=exclusive-chip.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Closed catalog. Last chip wins. Not an open `design-critique:*` glob. */
|
|
2
|
+
export const DESIGN_CRITIQUE_CATALOG_CHIPS = [
|
|
3
|
+
"design-critique:mechanism-shaped",
|
|
4
|
+
"design-critique:triage-ready",
|
|
5
|
+
];
|
|
6
|
+
const CATALOG = new Set(DESIGN_CRITIQUE_CATALOG_CHIPS);
|
|
7
|
+
export function isDesignCritiqueCatalogChip(name) {
|
|
8
|
+
return CATALOG.has(name);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Remaining-set replace: GET current, drop the other catalog names, keep other
|
|
12
|
+
* facets, then PUT/PATCH this list. One write. No DELETE-then-POST window.
|
|
13
|
+
*/
|
|
14
|
+
export function remainingSetAfterDesignCritiqueChip(current, nextChip) {
|
|
15
|
+
if (!isDesignCritiqueCatalogChip(nextChip)) {
|
|
16
|
+
throw new Error(`not a design-critique catalog chip: ${nextChip} (closed set: ${DESIGN_CRITIQUE_CATALOG_CHIPS.join(", ")})`);
|
|
17
|
+
}
|
|
18
|
+
const remaining = current.filter((name) => !CATALOG.has(name));
|
|
19
|
+
remaining.push(nextChip);
|
|
20
|
+
return remaining;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fold exclusive catalog-chip replace into a LabelClient.apply add/remove
|
|
24
|
+
* mutation. Production writer: ScmLabelClient.apply.
|
|
25
|
+
*/
|
|
26
|
+
export function mergeDesignCritiqueExclusiveIntoApply(current, add, remove) {
|
|
27
|
+
const catalogAdds = add.filter(isDesignCritiqueCatalogChip);
|
|
28
|
+
if (catalogAdds.length === 0) {
|
|
29
|
+
return { add: [...add], remove: [...remove] };
|
|
30
|
+
}
|
|
31
|
+
const nextChip = catalogAdds[catalogAdds.length - 1];
|
|
32
|
+
const delta = designCritiqueChipApplyDelta(current, nextChip);
|
|
33
|
+
const addSet = new Set(add.filter((name) => !isDesignCritiqueCatalogChip(name) || name === nextChip));
|
|
34
|
+
for (const name of delta.add)
|
|
35
|
+
addSet.add(name);
|
|
36
|
+
const removeSet = new Set(remove.filter((name) => name !== nextChip));
|
|
37
|
+
for (const name of delta.remove)
|
|
38
|
+
removeSet.add(name);
|
|
39
|
+
removeSet.delete(nextChip);
|
|
40
|
+
return { add: [...addSet], remove: [...removeSet] };
|
|
41
|
+
}
|
|
42
|
+
/** Delta for LabelClient.apply (one add+remove mutation). */
|
|
43
|
+
export function designCritiqueChipApplyDelta(current, nextChip) {
|
|
44
|
+
if (!isDesignCritiqueCatalogChip(nextChip)) {
|
|
45
|
+
throw new Error(`not a design-critique catalog chip: ${nextChip} (closed set: ${DESIGN_CRITIQUE_CATALOG_CHIPS.join(", ")})`);
|
|
46
|
+
}
|
|
47
|
+
const currentSet = new Set(current);
|
|
48
|
+
const remove = DESIGN_CRITIQUE_CATALOG_CHIPS.filter((name) => name !== nextChip && currentSet.has(name));
|
|
49
|
+
const add = currentSet.has(nextChip) ? [] : [nextChip];
|
|
50
|
+
return { add, remove };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Exclusive catalog-chip write via LabelClient.apply. GET current, then one
|
|
54
|
+
* apply(add, remove). Does not PUT a naive full wipe.
|
|
55
|
+
*/
|
|
56
|
+
export function applyDesignCritiqueCatalogChip(client, repo, issueNumber, nextChip) {
|
|
57
|
+
const current = client.fetchLabels(repo, issueNumber);
|
|
58
|
+
const { add, remove } = designCritiqueChipApplyDelta(current, nextChip);
|
|
59
|
+
if (add.length > 0 || remove.length > 0) {
|
|
60
|
+
client.apply(repo, issueNumber, add, remove);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
remaining: remainingSetAfterDesignCritiqueChip(current, nextChip),
|
|
64
|
+
add,
|
|
65
|
+
remove,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=exclusive-chip.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Parent-side substantiation deposit + fail-closed evaluator (#3651 / ADR-006). */
|
|
2
|
+
export type AuditReading = "measured" | "asserted";
|
|
3
|
+
export type AuditRole = "parent" | "critic" | "triage";
|
|
4
|
+
export type AuditPremise = {
|
|
5
|
+
markerId: string;
|
|
6
|
+
sha?: string;
|
|
7
|
+
pointer?: string;
|
|
8
|
+
reading?: AuditReading;
|
|
9
|
+
introducedByRole: AuditRole;
|
|
10
|
+
/** Changes classification, residual, or next-build contract. */
|
|
11
|
+
loadBearing: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type AuditClearance = {
|
|
14
|
+
markerId: string;
|
|
15
|
+
clearedByRole: AuditRole;
|
|
16
|
+
/** Critic artifact names this marker as its audit target. */
|
|
17
|
+
targetsMarker: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type AuditEnvelope = {
|
|
20
|
+
/** Marker ids listed on `audit-targets:`. Empty means the field was omitted. */
|
|
21
|
+
auditTargets: string[];
|
|
22
|
+
/** Envelope explicitly set `audit-targets: none`. */
|
|
23
|
+
declaredNone: boolean;
|
|
24
|
+
};
|
|
25
|
+
export type AuditBindAttempt = {
|
|
26
|
+
allAcceptMap: boolean;
|
|
27
|
+
/** Marker ids the bind record still lists as unresolved. */
|
|
28
|
+
unresolvedMarkerIds: string[];
|
|
29
|
+
};
|
|
30
|
+
export type ParentAuditDeposit = {
|
|
31
|
+
premises: readonly AuditPremise[];
|
|
32
|
+
clearances: readonly AuditClearance[];
|
|
33
|
+
envelopes: readonly AuditEnvelope[];
|
|
34
|
+
/** Ids the next critic envelope must name. */
|
|
35
|
+
namedAuditTargets?: readonly string[];
|
|
36
|
+
bindAttempt?: AuditBindAttempt;
|
|
37
|
+
};
|
|
38
|
+
export type AuditFailureCode = "missing-token" | "parent-self-clear" | "silent-clear" | "bind-unresolved" | "envelope-omits-target" | "marker-collision";
|
|
39
|
+
export type AuditFailure = {
|
|
40
|
+
code: AuditFailureCode;
|
|
41
|
+
detail: string;
|
|
42
|
+
};
|
|
43
|
+
export declare function parseAuditToken(token: string): {
|
|
44
|
+
markerId: string;
|
|
45
|
+
sha: string;
|
|
46
|
+
pointer: string;
|
|
47
|
+
reading: AuditReading;
|
|
48
|
+
} | null;
|
|
49
|
+
export declare function formatAuditToken(parts: {
|
|
50
|
+
markerId: string;
|
|
51
|
+
sha: string;
|
|
52
|
+
pointer: string;
|
|
53
|
+
reading: AuditReading;
|
|
54
|
+
}): string;
|
|
55
|
+
export declare function evaluateParentAudit(deposit: ParentAuditDeposit): {
|
|
56
|
+
ok: boolean;
|
|
57
|
+
failures: AuditFailure[];
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=parent-audit.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** Parent-side substantiation deposit + fail-closed evaluator (#3651 / ADR-006). */
|
|
2
|
+
const TOKEN_RE = /^audit:([A-Za-z0-9._-]+) sha=([0-9a-fA-F]{7,40}) pointer=(\S+) reading=(measured|asserted)$/;
|
|
3
|
+
export function parseAuditToken(token) {
|
|
4
|
+
const match = TOKEN_RE.exec(token.trim());
|
|
5
|
+
if (!match)
|
|
6
|
+
return null;
|
|
7
|
+
return {
|
|
8
|
+
markerId: match[1] ?? "",
|
|
9
|
+
sha: match[2] ?? "",
|
|
10
|
+
pointer: match[3] ?? "",
|
|
11
|
+
reading: (match[4] ?? "asserted"),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function formatAuditToken(parts) {
|
|
15
|
+
return `audit:${parts.markerId} sha=${parts.sha} pointer=${parts.pointer} reading=${parts.reading}`;
|
|
16
|
+
}
|
|
17
|
+
function independentlyCleared(markerId, clearances, colliding) {
|
|
18
|
+
if (colliding.has(markerId))
|
|
19
|
+
return false;
|
|
20
|
+
return clearances.some((c) => c.markerId === markerId && c.clearedByRole === "critic" && c.targetsMarker);
|
|
21
|
+
}
|
|
22
|
+
function collidingMarkerIds(premises) {
|
|
23
|
+
const counts = new Map();
|
|
24
|
+
for (const premise of premises) {
|
|
25
|
+
if (!premise.loadBearing)
|
|
26
|
+
continue;
|
|
27
|
+
counts.set(premise.markerId, (counts.get(premise.markerId) ?? 0) + 1);
|
|
28
|
+
}
|
|
29
|
+
return new Set([...counts.entries()].filter(([, n]) => n > 1).map(([id]) => id));
|
|
30
|
+
}
|
|
31
|
+
export function evaluateParentAudit(deposit) {
|
|
32
|
+
const failures = [];
|
|
33
|
+
const colliding = collidingMarkerIds(deposit.premises);
|
|
34
|
+
for (const id of colliding) {
|
|
35
|
+
failures.push({
|
|
36
|
+
code: "marker-collision",
|
|
37
|
+
detail: `load-bearing premises share marker ${id}`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
for (const premise of deposit.premises) {
|
|
41
|
+
if (!premise.loadBearing)
|
|
42
|
+
continue;
|
|
43
|
+
if (!premise.sha || !premise.pointer || !premise.reading) {
|
|
44
|
+
failures.push({
|
|
45
|
+
code: "missing-token",
|
|
46
|
+
detail: `premise ${premise.markerId} is missing sha, pointer, or reading`,
|
|
47
|
+
});
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const token = formatAuditToken({
|
|
51
|
+
markerId: premise.markerId,
|
|
52
|
+
sha: premise.sha,
|
|
53
|
+
pointer: premise.pointer,
|
|
54
|
+
reading: premise.reading,
|
|
55
|
+
});
|
|
56
|
+
if (!parseAuditToken(token)) {
|
|
57
|
+
failures.push({
|
|
58
|
+
code: "missing-token",
|
|
59
|
+
detail: `premise ${premise.markerId} token failed grammar`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const clearance of deposit.clearances) {
|
|
64
|
+
if (clearance.clearedByRole === "parent") {
|
|
65
|
+
failures.push({
|
|
66
|
+
code: "parent-self-clear",
|
|
67
|
+
detail: `parent cleared marker ${clearance.markerId}`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const computedUnresolved = deposit.premises
|
|
72
|
+
.filter((p) => p.loadBearing && !independentlyCleared(p.markerId, deposit.clearances, colliding))
|
|
73
|
+
.map((p) => p.markerId);
|
|
74
|
+
const named = deposit.namedAuditTargets ?? [];
|
|
75
|
+
if (named.length > 0) {
|
|
76
|
+
if (deposit.envelopes.length === 0) {
|
|
77
|
+
failures.push({
|
|
78
|
+
code: "envelope-omits-target",
|
|
79
|
+
detail: `no envelope named audit targets ${named.join(",")}`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
for (const envelope of deposit.envelopes) {
|
|
83
|
+
if (envelope.declaredNone) {
|
|
84
|
+
failures.push({
|
|
85
|
+
code: "envelope-omits-target",
|
|
86
|
+
detail: "envelope declared none while named audit targets exist",
|
|
87
|
+
});
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
for (const id of named) {
|
|
91
|
+
if (!envelope.auditTargets.includes(id)) {
|
|
92
|
+
failures.push({
|
|
93
|
+
code: "envelope-omits-target",
|
|
94
|
+
detail: `envelope omitted audit target ${id}`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const bind = deposit.bindAttempt;
|
|
101
|
+
if (bind) {
|
|
102
|
+
const declared = new Set(bind.unresolvedMarkerIds);
|
|
103
|
+
for (const id of computedUnresolved) {
|
|
104
|
+
if (!declared.has(id)) {
|
|
105
|
+
failures.push({
|
|
106
|
+
code: "silent-clear",
|
|
107
|
+
detail: `marker ${id} dropped from unresolved without critic clearance`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const unresolvedForBind = [...new Set([...computedUnresolved, ...bind.unresolvedMarkerIds])];
|
|
112
|
+
if (bind.allAcceptMap && unresolvedForBind.length > 0) {
|
|
113
|
+
failures.push({
|
|
114
|
+
code: "bind-unresolved",
|
|
115
|
+
detail: `all-accept bind with unresolved markers ${unresolvedForBind.join(",")}`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { ok: failures.length === 0, failures };
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=parent-audit.js.map
|
package/dist/doctor/checks.d.ts
CHANGED
|
@@ -106,6 +106,12 @@ export declare function checkCompletedLifecycleConsistency(projectRoot: string):
|
|
|
106
106
|
* historical pre-#2862 corpora do not red-light doctor; scope:complete fails closed.
|
|
107
107
|
*/
|
|
108
108
|
export declare function checkCompletedOpenItems(projectRoot: string): CheckResult;
|
|
109
|
+
/**
|
|
110
|
+
* Surface completed/ artifacts that never passed runTransition (#3679).
|
|
111
|
+
* Exit-exempt so historical corpora do not red-light doctor; the change-set
|
|
112
|
+
* verify (`verify:completed-write-guard`) fails closed on new adds.
|
|
113
|
+
*/
|
|
114
|
+
export declare function checkCompletedUnguardedWrite(projectRoot: string): CheckResult;
|
|
109
115
|
/**
|
|
110
116
|
* Surface invalid coverageDebt + checkResume typed blocks (#3314).
|
|
111
117
|
* Absent / default / valid typed values pass. Invalid blocks skip (advisory,
|
package/dist/doctor/checks.js
CHANGED
|
@@ -6,6 +6,7 @@ import { detectDualLayout, detectLegacyLayout, dualLayoutSignpostLine, legacyLay
|
|
|
6
6
|
import { detectCanonicalVendoredManifest, isNpmManaged, NPM_MANAGED_SENTINEL_KEY, NPM_MANAGED_SENTINEL_VALUE, } from "../init-deposit/migrate.js";
|
|
7
7
|
import { resolveLifecycleRoot } from "../layout/resolve.js";
|
|
8
8
|
import { scanCompletedLifecycleConsistency } from "../lifecycle/completed-consistency.js";
|
|
9
|
+
import { scanCompletedWriteCorpus } from "../lifecycle/completed-write-guard.js";
|
|
9
10
|
import { resolveCheckResume } from "../policy/check-resume.js";
|
|
10
11
|
import { resolveCoverageDebt } from "../policy/coverage-debt.js";
|
|
11
12
|
import { classifyXbriefSchemaDistance } from "../staleness-tickler/probe-xbrief.js";
|
|
@@ -34,6 +35,7 @@ export const DOCTOR_ADVISORY_FAIL_CHECKS = new Set([
|
|
|
34
35
|
"stale-xbrief-schema-deposit",
|
|
35
36
|
"typescript-7-side-by-side",
|
|
36
37
|
"completed-open-items",
|
|
38
|
+
"completed-unguarded-write",
|
|
37
39
|
"coverage-check-resume-policy",
|
|
38
40
|
]);
|
|
39
41
|
/** True when a check fail must stay a warning (not lastErrorCount / exit 1). */
|
|
@@ -1167,6 +1169,37 @@ export function checkCompletedOpenItems(projectRoot) {
|
|
|
1167
1169
|
},
|
|
1168
1170
|
};
|
|
1169
1171
|
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Surface completed/ artifacts that never passed runTransition (#3679).
|
|
1174
|
+
* Exit-exempt so historical corpora do not red-light doctor; the change-set
|
|
1175
|
+
* verify (`verify:completed-write-guard`) fails closed on new adds.
|
|
1176
|
+
*/
|
|
1177
|
+
export function checkCompletedUnguardedWrite(projectRoot) {
|
|
1178
|
+
const checkName = "completed-unguarded-write";
|
|
1179
|
+
const result = scanCompletedWriteCorpus(projectRoot);
|
|
1180
|
+
if (result.findings.length === 0) {
|
|
1181
|
+
return {
|
|
1182
|
+
name: checkName,
|
|
1183
|
+
status: "pass",
|
|
1184
|
+
detail: "Completed write-guard OK (#3679): no completed/ artifacts lack a runTransition write",
|
|
1185
|
+
data: { finding_count: 0, scanned: result.scanned },
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
const message = `Completed unguarded writes (#3679, advisory). ` +
|
|
1189
|
+
`${result.findings.length} artifact(s) under completed/ have no runTransition write:\n` +
|
|
1190
|
+
result.findings.map((f) => ` - ${f.detail}`).join("\n") +
|
|
1191
|
+
`\nNew adds fail closed via verify:completed-write-guard; historical corpus is advisory.`;
|
|
1192
|
+
return {
|
|
1193
|
+
name: checkName,
|
|
1194
|
+
status: "fail",
|
|
1195
|
+
detail: message,
|
|
1196
|
+
data: {
|
|
1197
|
+
finding_count: result.findings.length,
|
|
1198
|
+
advisory: true,
|
|
1199
|
+
findings: result.findings.map((f) => ({ rel_path: f.relPath, detail: f.detail })),
|
|
1200
|
+
},
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1170
1203
|
/**
|
|
1171
1204
|
* Surface invalid coverageDebt + checkResume typed blocks (#3314).
|
|
1172
1205
|
* Absent / default / valid typed values pass. Invalid blocks skip (advisory,
|
|
@@ -1248,6 +1281,7 @@ export function runChecksImpl(projectRoot, seams = {}) {
|
|
|
1248
1281
|
checks.push(checkCoverageCheckResumePolicy(projectRoot));
|
|
1249
1282
|
checks.push(checkCompletedLifecycleConsistency(projectRoot));
|
|
1250
1283
|
checks.push(checkCompletedOpenItems(projectRoot));
|
|
1284
|
+
checks.push(checkCompletedUnguardedWrite(projectRoot));
|
|
1251
1285
|
return {
|
|
1252
1286
|
projectRoot,
|
|
1253
1287
|
installRoot: null,
|
|
@@ -1270,6 +1304,7 @@ export function runChecksImpl(projectRoot, seams = {}) {
|
|
|
1270
1304
|
checks.push(checkCoverageCheckResumePolicy(projectRoot));
|
|
1271
1305
|
checks.push(checkCompletedLifecycleConsistency(projectRoot));
|
|
1272
1306
|
checks.push(checkCompletedOpenItems(projectRoot));
|
|
1307
|
+
checks.push(checkCompletedUnguardedWrite(projectRoot));
|
|
1273
1308
|
return {
|
|
1274
1309
|
projectRoot,
|
|
1275
1310
|
installRoot,
|
package/dist/freshness/bind.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Host-agnostic storage:
|
|
5
5
|
* - Default (no sessionId): `.deft/session-bind.json` — single-operator convenience.
|
|
6
|
-
* - With sessionId: `.deft/session-binds/<
|
|
6
|
+
* - With sessionId: `.deft/session-binds/<sha256-slice>.json` — isolated multi-session binds.
|
|
7
7
|
*
|
|
8
8
|
* Multi-agent hosts MUST pass a stable host session identity when binding and
|
|
9
9
|
* reporting so one session cannot certify another as current.
|
|
@@ -16,10 +16,39 @@ export declare const SESSION_BINDS_DIR_REL: string;
|
|
|
16
16
|
export declare function sessionBindPath(projectRoot: string, sessionId?: string | null): string;
|
|
17
17
|
/**
|
|
18
18
|
* Stable filesystem-safe file name for a host session id.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
*
|
|
20
|
+
* Hash only (#3768): a 24-hex SHA-256 slice of the id. Hex cannot carry a path
|
|
21
|
+
* separator, so path injection is structurally impossible, and the name stays
|
|
22
|
+
* derivable from the id — lookup is one computed path, never a directory scan.
|
|
23
|
+
*
|
|
24
|
+
* Records written before #3768 also carried the first 32 characters of the id
|
|
25
|
+
* (`legacySessionFileName`). Dropping that prefix is hygiene, not a security
|
|
26
|
+
* fix: the same id is already published in `.deft/occupancy.json`,
|
|
27
|
+
* `.deft/ritual-state.json` and the bind record body (#3611 / #3754).
|
|
28
|
+
*
|
|
29
|
+
* Coupling warning: if `.deft/` JSON bodies are ever tightened (say to `0600`)
|
|
30
|
+
* while names still carry the prefix, the prefix becomes independently
|
|
31
|
+
* load-bearing — directory read permission alone would recover an id that the
|
|
32
|
+
* file mode was meant to protect. Anyone tightening those modes must keep bind
|
|
33
|
+
* names prefix-free.
|
|
21
34
|
*/
|
|
22
35
|
export declare function safeSessionFileName(sessionId: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Pre-#3768 record name: sanitized 32-character id prefix plus the same hash.
|
|
38
|
+
*
|
|
39
|
+
* Existing records are **tolerated, not migrated**. `readBoundGeneration` falls
|
|
40
|
+
* back to this name when the hash-only record is absent, so freshness pins
|
|
41
|
+
* written before the rename are not orphaned; writes always use the hash-only
|
|
42
|
+
* name, so the next bind supersedes the legacy record. Both names derive from
|
|
43
|
+
* the id, so the fallback stays O(1) and adds no directory scan.
|
|
44
|
+
*
|
|
45
|
+
* Character filter is O(n) (no regex) — CodeQL poly-redos on uncontrolled ids.
|
|
46
|
+
*/
|
|
47
|
+
export declare function legacySessionFileName(sessionId: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Derived path of a pre-#3768 record. Read fallback only — never a write target.
|
|
50
|
+
*/
|
|
51
|
+
export declare function legacySessionBindPath(projectRoot: string, sessionId: string): string;
|
|
23
52
|
export interface BindSessionOptions {
|
|
24
53
|
readonly sessionId?: string | null;
|
|
25
54
|
readonly nowIso?: string;
|