testguard-cli 0.15.1 → 0.17.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/CHANGELOG.md +82 -0
- package/README.md +196 -17
- package/package.json +1 -1
- package/spec/GATE-SEMANTICS.md +316 -0
- package/spec/README.md +27 -2
- package/spec/lib/authoring-input.mjs +40 -0
- package/spec/lib/origins.mjs +56 -0
- package/spec/lib/validate.mjs +129 -1
- package/spec/schemas/annotations.schema.json +65 -0
- package/spec/schemas/authoring-input.schema.json +67 -0
- package/spec/schemas/brief.schema.json +2 -0
- package/spec/schemas/claims.schema.json +8 -8
- package/spec/schemas/common.schema.json +73 -0
- package/spec/schemas/evidence.schema.json +41 -8
- package/spec/schemas/status.schema.json +31 -2
- package/src/brief/brief.mjs +11 -4
- package/src/claims/annotation-placement.mjs +357 -0
- package/src/claims/annotations.mjs +61 -2
- package/src/cli.mjs +40 -5
- package/src/commands/admit.mjs +21 -3
- package/src/commands/annotations.mjs +67 -0
- package/src/commands/claims.mjs +46 -6
- package/src/commands/probe.mjs +50 -7
- package/src/commands/replay.mjs +7 -0
- package/src/commands/scaffold.mjs +49 -2
- package/src/commands/sweep.mjs +7 -0
- package/src/evidence/writer.mjs +12 -4
- package/src/gate/changed.mjs +5 -1
- package/src/git.mjs +4 -4
- package/src/init/init.mjs +6 -0
- package/src/init/templates/SKILL.md +78 -0
- package/src/probe/attribution.mjs +19 -0
- package/src/probe/contention.mjs +20 -2
- package/src/probe/cost.mjs +22 -9
- package/src/probe/inject.mjs +2 -6
- package/src/probe/origin-policy.mjs +69 -0
- package/src/probe/policy-freshness.mjs +74 -0
- package/src/probe/probe.mjs +74 -45
- package/src/probe/runners/discovery.mjs +2 -0
- package/src/probe/runners/jest.mjs +2 -2
- package/src/probe/runners/lifecycle.mjs +58 -0
- package/src/probe/runners/playwright.mjs +6 -2
- package/src/probe/runners/python.mjs +10 -4
- package/src/probe/runners/shared.mjs +12 -6
- package/src/probe/runners/vitest.mjs +3 -3
- package/src/probe/universe.mjs +47 -0
- package/src/probe/worktree.mjs +3 -1
- package/src/render.mjs +17 -1
- package/src/replay/replay.mjs +8 -4
- package/src/scaffold/admission.mjs +117 -0
- package/src/scaffold/append.mjs +41 -0
- package/src/scaffold/apply-append.mjs +71 -0
- package/src/scaffold/fix-input.mjs +119 -0
- package/src/scaffold/fix-paths.mjs +90 -0
- package/src/scaffold/intent.mjs +2 -0
- package/src/scaffold/open-append.mjs +44 -0
- package/src/scaffold/plan-append.mjs +37 -0
- package/src/scaffold/plan-intent-input.mjs +38 -0
- package/src/scaffold/prepare-append.mjs +46 -0
- package/src/scaffold/request.mjs +30 -0
- package/src/scaffold/scaffold.mjs +23 -10
- package/src/status/status.mjs +42 -10
- package/src/sweep/pool.mjs +33 -0
- package/src/sweep/sweep.mjs +9 -4
package/spec/GATE-SEMANTICS.md
CHANGED
|
@@ -1,5 +1,129 @@
|
|
|
1
1
|
# Gate semantics
|
|
2
2
|
|
|
3
|
+
## Declared claim origins
|
|
4
|
+
|
|
5
|
+
Status rechecks every recorded automatic-discovery dependency hash, including
|
|
6
|
+
negative candidate tests, barrels and resolver configuration. Edited, deleted,
|
|
7
|
+
unreadable or non-regular/escaping inputs make the evidence stale without
|
|
8
|
+
rewriting its verdicts. Legacy omission remains readable but does not establish
|
|
9
|
+
a complete origin-policy freshness binding. Rechecking recorded dependencies
|
|
10
|
+
alone does not detect a newly added test/configuration: native test-universe
|
|
11
|
+
recomputation remains required before origin-policy activation.
|
|
12
|
+
|
|
13
|
+
Canonical and recorded claim sources share the closed kinds `spec`, `adr`,
|
|
14
|
+
`annotation`, `comment`, `manual`, `doc`, `bug`, `incident`, `review`, `inferred`.
|
|
15
|
+
These are declarations, not authentication of independence, chronology,
|
|
16
|
+
production occurrence or correctness. The optional reference remains opaque
|
|
17
|
+
metadata (at most 512 characters); verification does not fetch it. Producer
|
|
18
|
+
provenance and measured test independence are separate signals.
|
|
19
|
+
Adding kinds does not change verdicts, baseline/fingerprint identity or default
|
|
20
|
+
gates. Existing ranking weights stay unchanged; new kinds use the existing 0.9
|
|
21
|
+
fallback, not an invented strength hierarchy. Existing documents remain valid
|
|
22
|
+
under the candidate reader; older closed-schema readers reject new kinds.
|
|
23
|
+
Optional `origins` summaries on evidence, status and brief have closed kind
|
|
24
|
+
buckets and safe nonnegative integer counts. Distinct claim buckets plus
|
|
25
|
+
`mixed` equal the claim total; record buckets equal the record total. Status
|
|
26
|
+
uses `basis: declared`, omits records, has zero mixed claims and binds its
|
|
27
|
+
total to current `counts.claims`, even when evidence is stale. Evidence and
|
|
28
|
+
brief use `basis: recorded` and require record counts. Evidence validation
|
|
29
|
+
recomputes from every actual record regardless of verdict: each claim ID
|
|
30
|
+
counts once, with conflicting source kind OR reference counted as one mixed
|
|
31
|
+
claim. Object-key order is not a conflict; absent and empty references differ.
|
|
32
|
+
Brief totals bind to the complete summary, not its capped/filtered items.
|
|
33
|
+
Status and brief omit the source projections needed to independently validate
|
|
34
|
+
their kind split; arithmetic validity is not source authentication. Legacy
|
|
35
|
+
documents without summaries remain valid; old closed readers reject them.
|
|
36
|
+
Status supplies current declared origins once validated claims are available,
|
|
37
|
+
including empty, unprobed, invalid-anchor and stale-evidence states. Probe
|
|
38
|
+
evidence and evidence-backed briefs supply recorded origins from all records,
|
|
39
|
+
including killed and non-killed records, before caps or baseline/floor filtering.
|
|
40
|
+
Human status/probe/brief text and Markdown label the basis and print "declared
|
|
41
|
+
origins, not authenticated independence". Evidence-free briefs and projects
|
|
42
|
+
without a claims file omit the summary rather than inventing declarations.
|
|
43
|
+
Probe JSON retains status's current declared basis; saved evidence has its own
|
|
44
|
+
recorded basis. These informational fields do not change states, next actions,
|
|
45
|
+
exit codes, fingerprints or default gates. Opt-in origin policy remains pending.
|
|
46
|
+
|
|
47
|
+
## Explicit annotation authoring
|
|
48
|
+
|
|
49
|
+
Ordinary `claims` reports a separate `annotationAdvisory` with distinct sorted
|
|
50
|
+
`missingIds` and informational `notes`; status uses its existing informational
|
|
51
|
+
`notes`. Missing links reduce discoverability and annotation reconciliation,
|
|
52
|
+
not exact-anchor validation. Advice supplies `testguard claims --annotate` for
|
|
53
|
+
read-only preview; it never changes state, next verification action, verdicts,
|
|
54
|
+
source kinds or exit codes. Annotation-sourced missing IDs still retain the
|
|
55
|
+
existing claims drift error. Scanned occurrences are lexical links, not proof
|
|
56
|
+
of intent, source ownership or authenticated provenance.
|
|
57
|
+
Status advisory scanning is bounded to 10,000 directory entries, 1,000 files,
|
|
58
|
+
64 directory levels, 256 KiB per file and 2 MiB total read bytes. Read failures,
|
|
59
|
+
unsafe file identities or limits produce unavailable advice, never a claim
|
|
60
|
+
that an ID is absent from an incomplete scan. Verification state/next still
|
|
61
|
+
comes from its existing inputs. For custom claims, preserve the original
|
|
62
|
+
`--claims` argument when following the preview suggestion.
|
|
63
|
+
Scanner and placement ID prefixes use mandatory separators between repeated
|
|
64
|
+
alphanumeric groups, avoiding overlapping quantifiers on tokens lacking a
|
|
65
|
+
hyphen without changing the supported identifier vocabulary.
|
|
66
|
+
|
|
67
|
+
`claims --annotate` previews file-header placement without writes. Add `--apply`
|
|
68
|
+
to explicitly apply a newly computed plan; a previous invocation's preview is
|
|
69
|
+
not a saved approval token. Repeated/comma-separated `--claim` selects IDs;
|
|
70
|
+
unknown or empty IDs are usage errors, never an implicit all-claims selection.
|
|
71
|
+
Authoring flags are valid only on `claims`; `--apply` requires `--annotate`.
|
|
72
|
+
Authoring rejects unrelated inspection/output switches and writes results only
|
|
73
|
+
to stdout. It never updates claims metadata, evidence or baselines.
|
|
74
|
+
|
|
75
|
+
The closed `annotations` document describes authoring, not verification. It
|
|
76
|
+
contains selected IDs, admitted target/action summaries, refusals and, for
|
|
77
|
+
apply, touched/verified/unchanged files and explicit failure/recovery details.
|
|
78
|
+
It excludes source/proposed contents, inode identities, hashes and absolute
|
|
79
|
+
source/root paths. Preview exits 0 when applicable, 2 when refused. Apply exits
|
|
80
|
+
0 only for a fully verified apply (including idempotent no-op), 2 for refusal or
|
|
81
|
+
partial failure. Invalid invocation exits 3. No authoring result changes probe
|
|
82
|
+
verdicts, rank, coverage, source-kind declarations or authentication of intent.
|
|
83
|
+
Failure reasons are stage-specific public summaries: raw syscall details can
|
|
84
|
+
contain absolute input paths and remain private, never passed through to JSON.
|
|
85
|
+
|
|
86
|
+
Placement supports JavaScript/TypeScript and Python file-level line-comment
|
|
87
|
+
headers, not symbol ownership. Exact anchors remain checked without annotations.
|
|
88
|
+
Every selected target must pass conservative path, regular-file, UTF-8/newline,
|
|
89
|
+
anchor and size admission: 2 MiB per original/proposed file and 16 MiB aggregate
|
|
90
|
+
charged before reads plus proposal growth. Any refusal blocks all writes.
|
|
91
|
+
Preserve BOM, shebang, Python encoding cookies, newline style, inode and mode.
|
|
92
|
+
Only leading line-comment headers are placement witnesses, not string contents.
|
|
93
|
+
|
|
94
|
+
Apply reloads actual claims under an exclusive owned lock, replays all targets,
|
|
95
|
+
and fsyncs owner-only originals/manifest outside the canonical project before
|
|
96
|
+
writing. Recheck claims and targets immediately before each non-truncating
|
|
97
|
+
descriptor write; verify output, distinguish potentially touched from verified
|
|
98
|
+
files, and retain private recovery on partial failure. Reject the actual claims
|
|
99
|
+
file as a target, root/descendant symlinks and recovery inside the checkout.
|
|
100
|
+
Resolve parent aliases to bind the actual root. Never reclaim another writer's
|
|
101
|
+
lock or automatically roll back over an editor's changes. Stage and fsync the
|
|
102
|
+
final recovery journal before final naming. This is recoverable, not atomic:
|
|
103
|
+
arbitrary same-inode editors and process/power failure can race or interrupt
|
|
104
|
+
writes; directory durability across power loss is not guaranteed.
|
|
105
|
+
|
|
106
|
+
## Fault-specific defender selection
|
|
107
|
+
|
|
108
|
+
A fault's `defendedBy`, when present, overrides its claim's declaration.
|
|
109
|
+
An explicit empty fault list requests discovery and never falls back to the
|
|
110
|
+
claim. Without a fault override, a non-empty claim list is inherited; otherwise
|
|
111
|
+
defenders are discovered. New evidence records `defenders.selectionSource` as
|
|
112
|
+
`fault`, `claim`, or `discovery`, alongside the actual requested and resolved
|
|
113
|
+
sets. Empty requests use discovery and carry `discovered: true`, including an
|
|
114
|
+
explicit empty fault override. Non-empty requests are never marked discovered.
|
|
115
|
+
Dirty-input preflight, runner ownership, mock-awareness, input hashing and
|
|
116
|
+
baseline caching all operate on each fault's selected defender set.
|
|
117
|
+
|
|
118
|
+
Changing requested globs, resolved files or selection origin invalidates verdict
|
|
119
|
+
reuse. Legacy evidence remains readable but cannot authorize a new fault-level
|
|
120
|
+
override. A strict resolved subset after a previous kill is reported by `claims`
|
|
121
|
+
as a narrowing warning and requires re-probing; it is not a new verdict.
|
|
122
|
+
Cost reports include per-fault recorded run durations and selection origin.
|
|
123
|
+
Shared defenders identify the actual fault records that selected each file,
|
|
124
|
+
including multiple faults within one claim. File costs remain overlapping upper
|
|
125
|
+
bounds, not additive shares. Existing total/per-claim budget ceilings are unchanged.
|
|
126
|
+
|
|
3
127
|
How a Guard-spec tool decides whether CI goes red. Shared by every tool that
|
|
4
128
|
adopts the spec; each tool may add stricter rules, never looser ones.
|
|
5
129
|
|
|
@@ -87,6 +211,17 @@ Rules that follow from the table:
|
|
|
87
211
|
the fault is probed again: the cost of re-measuring is a run, the cost of
|
|
88
212
|
the other direction is a verdict nobody measured.
|
|
89
213
|
|
|
214
|
+
**Claim metadata is also an input to reuse.** The recorded claim id,
|
|
215
|
+
statement, severity, source (including its reference), and optional
|
|
216
|
+
producer must match the current claim. Object-key order is not a change;
|
|
217
|
+
field values and producer presence are. Metadata edits make `status`
|
|
218
|
+
stale and require a fresh probe, even when the fault and defender inputs
|
|
219
|
+
are identical. A reused record must never be silently relabelled as a
|
|
220
|
+
different requirement or origin. Legacy records remain readable; missing
|
|
221
|
+
metadata cannot establish equality with a current declaration. This
|
|
222
|
+
does not change verdicts or baseline fingerprints, and a source label
|
|
223
|
+
still does not authenticate independent intent.
|
|
224
|
+
|
|
90
225
|
9. **The fault must be the code that ran.** A green baseline proves the
|
|
91
226
|
harness is not reporting everything as broken. It proves nothing about the
|
|
92
227
|
opposite direction: a fault the interpreter never executes leaves the
|
|
@@ -446,6 +581,69 @@ Security- and money-shaped path names are explicit tie-breaking signals, never
|
|
|
446
581
|
semantic claims about a file. Churn, path risk and claim coverage remain raw
|
|
447
582
|
facts: they are never collapsed into one health score.
|
|
448
583
|
|
|
584
|
+
### Intent-first scaffold authoring
|
|
585
|
+
|
|
586
|
+
Unannotated generated draft groups use unfinished `TODO-CLAIM-N` identifiers,
|
|
587
|
+
not file/function-shaped names. Allocation is deterministic in scan order and
|
|
588
|
+
reserves explicit annotations and supplied existing IDs before choosing ordinals.
|
|
589
|
+
It never copies unrelated existing metadata into a generated placeholder. Explicit
|
|
590
|
+
claim IDs/annotations retain their existing behavior; neither annotations nor
|
|
591
|
+
placeholder IDs authenticate intent. Grouping remains mechanical candidate
|
|
592
|
+
organization, not invariant ownership. Sweep's pooled allocator keeps repeated
|
|
593
|
+
per-file placeholders distinct without changing fault content. Existing claims
|
|
594
|
+
and recorded evidence are not renamed. The claims schema/validator is unchanged:
|
|
595
|
+
these are valid draft IDs and still require supplied intent and review/probing.
|
|
596
|
+
|
|
597
|
+
Explicit `scaffold <source...> --into <existing-draft.json> --claim <ID>` appends
|
|
598
|
+
mechanically derived faults only to that existing selected claim. Preview JSON
|
|
599
|
+
is a conforming claims document (the existing claims schema and validator), not
|
|
600
|
+
verification evidence. `--json` creates no files, locks or recovery. Without it,
|
|
601
|
+
only the explicitly selected disposable draft is updated; unchanged repeats
|
|
602
|
+
preserve exact bytes/mtime. Supplied statements/origins/metadata and existing
|
|
603
|
+
faults/sibling claims remain intact. Review and probe are still required.
|
|
604
|
+
|
|
605
|
+
`--into` is scaffold-only, singular and requires one nonempty existing claim ID
|
|
606
|
+
and at least one source. Repeated/comma selections, `--out`, `--claims` and any
|
|
607
|
+
explicit unrelated option refuse with usage exit 3 before reading/writing.
|
|
608
|
+
Help/version retain their normal early-exit behavior. Ordinary scaffold remains
|
|
609
|
+
one-file; annotation `--apply` retains its separate claims-only meaning.
|
|
610
|
+
|
|
611
|
+
Refuse canonical `testguard.claims.json` names and built-in evidence/baseline
|
|
612
|
+
destinations, unsafe aliases/symlinks/hardlinks/nested projects and nonregular
|
|
613
|
+
inputs. Admission limits are 32 sources, 2 MiB per source/draft, 16 MiB aggregate
|
|
614
|
+
source bytes, 4096 generated proposals and 2 MiB serialized output. Caps refuse,
|
|
615
|
+
never truncate proposals. These are not whole-command performance guarantees.
|
|
616
|
+
|
|
617
|
+
Changed drafts require a cooperative owned exclusive lock, current input replay,
|
|
618
|
+
external private original/started/result recovery journals, nofollow descriptor
|
|
619
|
+
publication through `writeSpecDoc`, short-write handling, fsync and exact
|
|
620
|
+
byte/schema/path/source verification. Refusal, stale inputs, partial writes,
|
|
621
|
+
verification or cleanup/journal failure exit 2, report recovery when available,
|
|
622
|
+
and never print updated success. No automatic rollback, stale-lock reclamation,
|
|
623
|
+
atomic multi-file snapshot or power-loss transaction is promised. Exit 0 means
|
|
624
|
+
authoring completed, not defended coverage. Private recovery is not evidence.
|
|
625
|
+
|
|
626
|
+
Sweep pools per-file drafts before selection. Repeated claim IDs from equal
|
|
627
|
+
basenames/functions or annotations do not merge unrelated claims: reserve all
|
|
628
|
+
original IDs, then allocate unused numeric suffixes to collisions in full
|
|
629
|
+
target-path/original-ID order, within the existing 128-character limit. Metadata,
|
|
630
|
+
fault IDs, injection anchors and defenders are preserved. Drafts, selected probe
|
|
631
|
+
claims and finding lookup share these identities. This affects fresh collision
|
|
632
|
+
cases only; existing documents, verdicts, caps and exit rules remain compatible.
|
|
633
|
+
|
|
634
|
+
Scaffold's mechanically generated new claims declare `source.kind: inferred`,
|
|
635
|
+
including proposals grouped by an annotation. Their TODO asks for intended
|
|
636
|
+
observable behavior from a requirement, ADR, bug or incident: inputs, expected
|
|
637
|
+
outcome and forbidden outcome. Existing supplied claim metadata retains its
|
|
638
|
+
statement and declared origin under the existing `--claim` rules. Derived fault
|
|
639
|
+
provenance remains derived. No automatic source promotion or canonical write.
|
|
640
|
+
|
|
641
|
+
Human CLI, status authoring actions and the installed skill hand off independent
|
|
642
|
+
intent authoring. Record a supplied source kind/ref; when unavailable keep
|
|
643
|
+
inferred. References and annotations do not authenticate intent and are never
|
|
644
|
+
fetched. Passing tests do not establish independent intent. State, next action,
|
|
645
|
+
verdict and exit-code semantics are unchanged; this is authoring guidance.
|
|
646
|
+
|
|
449
647
|
When existing evidence is clean but more source modules are unclaimed than
|
|
450
648
|
claimed, the next action is to scaffold the highest-churn unclaimed module.
|
|
451
649
|
Earlier correctness states still win: invalid anchors, stale evidence and
|
|
@@ -559,6 +757,48 @@ evidence; they simply do not turn CI red.
|
|
|
559
757
|
|
|
560
758
|
## Exit codes
|
|
561
759
|
|
|
760
|
+
### Explicit declared-origin policy result contract
|
|
761
|
+
|
|
762
|
+
Optional `originPolicy` on evidence, status and brief (and status's probe `run`)
|
|
763
|
+
records an explicitly selected eligible-kind set, not authenticated independence.
|
|
764
|
+
Legacy absence is not a pass. `probe --require-origin <kinds>` selects a
|
|
765
|
+
repeatable comma-separated nonempty set from the closed source vocabulary.
|
|
766
|
+
It requires a complete current-project run with confirm >= 3 and refuses
|
|
767
|
+
`--claim`, `--allow-empty`, explicit `--ref` and `--ignore-dirty` as usage errors
|
|
768
|
+
before loading or writing. No policy is inferred when the option is absent.
|
|
769
|
+
After measurement, the command reloads current claims and collects the current
|
|
770
|
+
native runner universe under the same command budget, then binds targets,
|
|
771
|
+
resolved defenders and current discovery dependencies. Missing bindings or
|
|
772
|
+
discovery failure yields unavailable; custom/static runner universes cannot
|
|
773
|
+
certify this boundary. Expired command budgets still write no partial result.
|
|
774
|
+
This is point-in-time admission, not an atomic filesystem snapshot. Stored
|
|
775
|
+
results are not freshness authority. Offline status reuses only the recorded
|
|
776
|
+
eligible-kind selection, recomputes current declarations/complete identities,
|
|
777
|
+
and marks freshness unavailable without executing native discovery. This is
|
|
778
|
+
an audit projection, not implicit activation of a new gate: the existing
|
|
779
|
+
status state, next action and exit remain unchanged. Briefs preserve the
|
|
780
|
+
recorded policy before baseline filtering or item caps, labeled recorded-run
|
|
781
|
+
only and not current freshness. No absent policy is synthesized. A zero-item
|
|
782
|
+
cap cannot turn unproven faults into an all-defended message.
|
|
783
|
+
|
|
784
|
+
The closed result carries `state` (`passed`, `failed`, `unavailable`), sorted
|
|
785
|
+
unique `eligibleKinds`, current `claims`/`faults` denominators, sorted unique
|
|
786
|
+
`ineligibleClaims`, unique `nonKilledFaults` (`claimId`, `faultId` only), and
|
|
787
|
+
sorted unique `unavailableReasons`. References and paths are excluded.
|
|
788
|
+
Refusal reasons require unavailable; otherwise any ineligible claim or
|
|
789
|
+
non-killed fault requires failed. Only a nonempty failure-free universe can
|
|
790
|
+
pass. Baseline debt and severity floors cannot suppress policy failures.
|
|
791
|
+
|
|
792
|
+
Evidence validation binds actual non-killed identities, and, for an available
|
|
793
|
+
policy, unique recorded denominators, recorded eligibility, unmixed origins and
|
|
794
|
+
confirmed injection. Validation cannot authenticate current metadata or disk
|
|
795
|
+
freshness: evaluation must separately bind the complete current fault universe
|
|
796
|
+
and existing input freshness. Projected status/brief bind denominators, not
|
|
797
|
+
hidden source declarations. Brief policy describes its recorded run only.
|
|
798
|
+
Completed probe JSON with unavailable policy exits 2; failed policy or ordinary
|
|
799
|
+
new findings exits 1; passed policy without ordinary findings exits 0.
|
|
800
|
+
Partial/provisional invocations cannot carry an origin policy.
|
|
801
|
+
|
|
562
802
|
| Code | Meaning |
|
|
563
803
|
|---|---|
|
|
564
804
|
| `0` | No new gating findings at or above the severity floor. |
|
|
@@ -577,3 +817,79 @@ output is the existing `no-claims` status document, without run metadata;
|
|
|
577
817
|
it never reports a clean measurement. Missing or invalid claims still fail,
|
|
578
818
|
and selected claims cannot be silently skipped. Run `gate` separately to
|
|
579
819
|
enforce coverage of changed files during adoption. Integrations must opt in.
|
|
820
|
+
|
|
821
|
+
## Read-only authoring input (candidate)
|
|
822
|
+
|
|
823
|
+
`authoring-input` is metadata-only inspection, never claims or evidence. Its
|
|
824
|
+
closed purpose/verification/next fields explicitly state that verification was
|
|
825
|
+
not performed and independent intent remains to be supplied. Documents expose
|
|
826
|
+
only bounded project-relative file/hash/byte metadata, not text or absolute
|
|
827
|
+
roots. A digest or schema-valid report does not authenticate intent or freshness.
|
|
828
|
+
|
|
829
|
+
Fix inputs retain exact same-width commit/parent/path identities, untrusted
|
|
830
|
+
single-line subjects, selected root/nested scope and full partition accounting.
|
|
831
|
+
Visible rows exclude private/delegated names; their count stays in the total.
|
|
832
|
+
Deleted and unsupported rows cannot become supported source. Validators reject
|
|
833
|
+
duplicate/unsafe/private paths, inconsistent status/mode/zero-ID relations,
|
|
834
|
+
disposition/count mismatches, unsafe subjects and resource overruns. Empty
|
|
835
|
+
documents/inventories are admitted input metadata, not successful verification.
|
|
836
|
+
Readers cannot infer actual history completeness or current input admission
|
|
837
|
+
from shape alone; the runtime must use admitted local inputs without promotion.
|
|
838
|
+
|
|
839
|
+
`scaffold --from-document <local-text-path>` or `--from-fix <full-commit-ID>`
|
|
840
|
+
selects exactly one read-only authoring input. Both token dispatch and direct
|
|
841
|
+
handler entry reject repetition, mixed modes, positional sources and unrelated
|
|
842
|
+
explicit options (only JSON/help/version may accompany selection). Normal
|
|
843
|
+
help/version early exits remain unchanged. Usage refusal exits 3; input admission
|
|
844
|
+
or validated planning failure exits 2 with no success stdout. Success exits 0
|
|
845
|
+
for inspection only, never coverage. Both JSON and human modes are stdout-only.
|
|
846
|
+
JSON uses the validated metadata-only report; human rendering omits text,
|
|
847
|
+
subjects, absolute roots and historical filenames while retaining full fix
|
|
848
|
+
partition counts. Both name verification as not performed and require supplied
|
|
849
|
+
independent intent before reviewing/probing faults. One elapsed budget covers
|
|
850
|
+
input reads and validated serialization; scope resolution never retries with
|
|
851
|
+
fresh time. Existing ordinary scaffold/append/probe behavior is unchanged.
|
|
852
|
+
|
|
853
|
+
## Resource controls and measurement
|
|
854
|
+
|
|
855
|
+
Built-in runners default to one worker. `--workers N` raises the worker ceiling
|
|
856
|
+
for Vitest, Jest and Playwright; `--serial` overrides it to one. Python remains
|
|
857
|
+
serial and disables pytest-xdist without installing or enabling any plugin.
|
|
858
|
+
Faults and mixed-runner partitions remain sequential. This is a worker ceiling,
|
|
859
|
+
not OS CPU/memory containment; test-created threads, native libraries and custom
|
|
860
|
+
`--runner-cmd` commands manage their own concurrency. Native discovery is bounded
|
|
861
|
+
and uses one worker where applicable.
|
|
862
|
+
|
|
863
|
+
Runner stdout is drained without retention; stderr retains only its last 65,536
|
|
864
|
+
characters. Complete JSON reports are read under the existing 16 MiB discovery
|
|
865
|
+
report admission limit. An oversized, unreadable or incomplete report is an error,
|
|
866
|
+
never an assertion kill. No confirmation count or verdict rule changes.
|
|
867
|
+
|
|
868
|
+
Cancellation terminates only registered children and their attributable process
|
|
869
|
+
trees, waits up to one second for close, restores live mutations and removes the
|
|
870
|
+
registered scratch worktrees. It exits 130 without publishing partial evidence.
|
|
871
|
+
Reparented daemons remain outside the portable containment guarantee. Git
|
|
872
|
+
subprocesses have a 30-second hard deadline; the whole-command budget remains
|
|
873
|
+
cooperative around synchronous setup, and bounded OS cleanup can outlive it.
|
|
874
|
+
|
|
875
|
+
Optional `run.measurements` records monotonic elapsed time, fresh runner execution
|
|
876
|
+
time, invocation count and remaining setup/discovery/other overhead. Each real
|
|
877
|
+
runner invocation is charged once, including negative controls and mixed-runner
|
|
878
|
+
partitions; reused records and cached baselines create no invocation charge.
|
|
879
|
+
`elapsedMs = runnerMs + overheadMs`. Optional `run.workers` records the built-in
|
|
880
|
+
worker ceiling and must be one for serial execution; custom commands omit it.
|
|
881
|
+
Native evidence reuse requires the same recorded worker ceiling and serial policy.
|
|
882
|
+
Older native evidence with no worker ceiling is remeasured; native and opaque
|
|
883
|
+
custom-command policies are not interchangeable.
|
|
884
|
+
`status.cost.measurements` copies these measurements when available. Historical
|
|
885
|
+
`totalMs` and cost gates retain their existing record attribution, including
|
|
886
|
+
reused durations and repeated shared baseline durations. Older evidence has no
|
|
887
|
+
measured breakdown; consumers must not infer it from record totals.
|
|
888
|
+
|
|
889
|
+
The existing Python interpreter cache is reset at probe and replay entry and
|
|
890
|
+
reused within that invocation. Uncached adapter resolution consumes the same
|
|
891
|
+
live per-run deadline as test execution.
|
|
892
|
+
|
|
893
|
+
The regression suite caps outer concurrency at two workers in CI. Interactive
|
|
894
|
+
runs reserve one available CPU and use at most two workers; a two-core machine
|
|
895
|
+
therefore uses one. Nested built-in runners retain their one-worker default.
|
package/spec/README.md
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# claimspec — the contract spine
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
The candidate optional `originPolicy` result is a closed declared-origin policy
|
|
4
|
+
audit on evidence/status/brief. It is not independent-origin authentication or
|
|
5
|
+
current freshness authority. `probe --require-origin` explicitly evaluates it
|
|
6
|
+
against current native/input bindings. Offline status recomputes declarations
|
|
7
|
+
without certifying freshness; brief preserves labeled recorded-run policy
|
|
8
|
+
before filtering/caps. See GATE-SEMANTICS.md.
|
|
9
|
+
|
|
10
|
+
Shared formats for declaring what must be true about a codebase, recording the
|
|
11
|
+
evidence that tried to falsify it, gating only what is new, and explicit authoring.
|
|
5
12
|
|
|
6
13
|
They are shared as *formats*, not as code. The first tools to adopt them are
|
|
7
14
|
[`docguard-cli`](https://www.npmjs.com/package/docguard-cli) (Node ESM) and
|
|
@@ -33,6 +40,8 @@ that carry claims, baselines, scoping and briefs do not care.
|
|
|
33
40
|
| `status` | [`schemas/status.schema.json`](schemas/status.schema.json) | Where the project is and what happens next — the single machine-readable truth every human rendering derives from. Surfaces faults whose content changed since they were probed, and changed files that carry no claim. |
|
|
34
41
|
| `replay` | [`schemas/replay.schema.json`](schemas/replay.schema.json) | Would this suite have caught the bugs that already escaped? Replays real fix commits: revert the source, remove the test the fix shipped, run what remains. A replayed bug is ground truth — a human already confirmed it was a defect — which is what a fault model is calibrated against. |
|
|
35
42
|
| `gate` | [`schemas/gate.schema.json`](schemas/gate.schema.json) | Claim coverage of one change: which changed files carry a claim, which are excused (and by which ignore entry), which are unclaimed. The delta gate for code that has no claim yet. |
|
|
43
|
+
| `annotations` | [`schemas/annotations.schema.json`](schemas/annotations.schema.json) | Explicit file-header authoring preview/apply, refusals and recoverable partial failures. Never verification, a probe verdict, or authentication of intent; internal source contents are excluded. |
|
|
44
|
+
| `authoring-input` | [`schemas/authoring-input.schema.json`](schemas/authoring-input.schema.json) | Candidate metadata-only document/fix input handoff. No source text, origin assignment, claims or verification; historical partitions retain excluded/deleted/unsupported counts. CLI exposure remains pending. |
|
|
36
45
|
|
|
37
46
|
Shared definitions (verdicts, fault classes, provenance, annotations) live in
|
|
38
47
|
[`schemas/common.schema.json`](schemas/common.schema.json). Gate behaviour —
|
|
@@ -48,6 +57,22 @@ rule is in `GATE-SEMANTICS.md`.
|
|
|
48
57
|
|
|
49
58
|
Schemas are JSON Schema 2020-12 and identified as `urn:claimspec:v1:<kind>`.
|
|
50
59
|
|
|
60
|
+
Claim sources share `common#/$defs/claimSourceKind`: `spec`, `adr`, `annotation`,
|
|
61
|
+
`comment`, `manual`, `doc`, `bug`, `incident`, `review`, `inferred`. They declare
|
|
62
|
+
intent origin, not authenticated independence or an evidence-strength ranking.
|
|
63
|
+
References remain opaque metadata; producer provenance is separate. Existing
|
|
64
|
+
documents remain readable, but old closed-schema readers reject the new kinds.
|
|
65
|
+
Optional `origins` contracts on status, evidence and brief separate distinct
|
|
66
|
+
claim counts from record counts. Evidence validation recomputes every kind and
|
|
67
|
+
mixed declaration from all records; status/brief can validate only arithmetic
|
|
68
|
+
and document totals because source projections are omitted. References are
|
|
69
|
+
never echoed in these summaries. Legacy documents without them remain valid;
|
|
70
|
+
older closed readers reject the additive field. Status emits current declared
|
|
71
|
+
origins; probe evidence and evidence-backed briefs emit recorded origins before
|
|
72
|
+
filtering/caps. Human renderings label these declarations as unauthenticated.
|
|
73
|
+
Opt-in origin policy is available on complete confirmed candidate probes;
|
|
74
|
+
offline projections remain audits, never current native freshness. See `GATE-SEMANTICS.md`.
|
|
75
|
+
|
|
51
76
|
## Conformance
|
|
52
77
|
|
|
53
78
|
`lib/validate.mjs` validates a document of any kind: JSON Schema first, then
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { extname } from 'node:path';
|
|
2
|
+
|
|
3
|
+
const PRIVATE = new Set(['.local', '.wolf', '.git', '.testguard', 'graphify-out']);
|
|
4
|
+
const SOURCE = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts', '.jsx', '.tsx', '.py']);
|
|
5
|
+
|
|
6
|
+
/** Shape validation runs first. These checks cannot authenticate the actual input. */
|
|
7
|
+
export function authoringInputErrors(doc) {
|
|
8
|
+
const errors = [];
|
|
9
|
+
const fail = (path, message) => errors.push({ path, message });
|
|
10
|
+
const checkPath = (file, path) => {
|
|
11
|
+
if (Buffer.byteLength(file) > 4096 || file.includes('\\') || /[\x00-\x1f\x7f-\x9f]/.test(file) || /^[A-Za-z]:/.test(file) || file.split('/').some(part => !part || part === '.' || part === '..' || PRIVATE.has(part))) fail(path, 'input path must be bounded, canonical and outside private/tooling namespaces');
|
|
12
|
+
};
|
|
13
|
+
if (Buffer.byteLength(JSON.stringify(doc)) > 1024 * 1024) fail('/', 'authoring input report exceeds byte limit');
|
|
14
|
+
const input = doc.input;
|
|
15
|
+
if (input.kind === 'document') {
|
|
16
|
+
checkPath(input.file, '/input/file');
|
|
17
|
+
if (!['.md', '.txt', '.rst', '.adoc'].includes(extname(input.file))) fail('/input/file', 'document format is unsupported');
|
|
18
|
+
return errors;
|
|
19
|
+
}
|
|
20
|
+
if (input.commit.length !== input.parent.length || input.commit === input.parent) fail('/input/parent', 'fix requires a distinct same-width parent identity');
|
|
21
|
+
if (Buffer.byteLength(input.subject) > 1024 || /[\x00-\x1f\x7f-\x9f]/.test(input.subject)) fail('/input/subject', 'commit subject must be bounded untrusted single-line text');
|
|
22
|
+
const { total, supported, deleted, unsupported, excluded } = input.counts;
|
|
23
|
+
if (total !== supported + deleted + unsupported + excluded || input.paths.length !== supported + deleted + unsupported) fail('/input/counts', 'complete path partition must retain excluded/deleted/unsupported denominators');
|
|
24
|
+
const seen = new Set(), actual = { supported: 0, deleted: 0, unsupported: 0 };
|
|
25
|
+
input.paths.forEach((row, i) => {
|
|
26
|
+
const path = `/input/paths/${i}`;
|
|
27
|
+
checkPath(row.file, `${path}/file`);
|
|
28
|
+
if (seen.has(row.file)) fail(`${path}/file`, 'visible path is duplicated');
|
|
29
|
+
seen.add(row.file);
|
|
30
|
+
if (row.oldId.length !== input.commit.length || row.newId.length !== input.commit.length) fail(path, 'path object identities must match commit width');
|
|
31
|
+
const oldZero = /^0+$/.test(row.oldId), newZero = /^0+$/.test(row.newId);
|
|
32
|
+
if ((row.oldMode === '000000') !== oldZero || (row.newMode === '000000') !== newZero ||
|
|
33
|
+
(row.status === 'A' ? !oldZero || newZero : row.status === 'D' ? oldZero || !newZero : oldZero || newZero)) fail(path, 'mode, status and zero identity must agree');
|
|
34
|
+
const disposition = row.status === 'D' ? 'deleted' : !['100644', '100755'].includes(row.newMode) || !SOURCE.has(extname(row.file)) ? 'unsupported' : 'supported';
|
|
35
|
+
if (row.disposition !== disposition) fail(`${path}/disposition`, 'disposition must derive from actual status, mode and supported extension');
|
|
36
|
+
actual[row.disposition]++;
|
|
37
|
+
});
|
|
38
|
+
if (actual.supported !== supported || actual.deleted !== deleted || actual.unsupported !== unsupported) fail('/input/counts', 'visible disposition counts must reproduce every path');
|
|
39
|
+
return errors;
|
|
40
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
+
|
|
4
|
+
// One vocabulary for admission and reporting; never an evidence-strength order.
|
|
5
|
+
const common = JSON.parse(readFileSync(new URL('../schemas/common.schema.json', import.meta.url), 'utf8'));
|
|
6
|
+
export const CLAIM_SOURCE_KINDS = Object.freeze([...common.$defs.claimSourceKind.enum]);
|
|
7
|
+
const kindSet = new Set(CLAIM_SOURCE_KINDS);
|
|
8
|
+
const counts = () => Object.fromEntries(CLAIM_SOURCE_KINDS.map((kind) => [kind, 0]));
|
|
9
|
+
|
|
10
|
+
// Consumers validate their full document first. These narrow guards prevent
|
|
11
|
+
// malformed direct calls from silently manufacturing a meaningful summary.
|
|
12
|
+
function declaration(claim) {
|
|
13
|
+
const source = claim?.source;
|
|
14
|
+
if (typeof claim?.id !== 'string' || !claim.id || !kindSet.has(source?.kind)
|
|
15
|
+
|| Object.keys(source).some((key) => key !== 'kind' && key !== 'ref')
|
|
16
|
+
|| (Object.hasOwn(source, 'ref') && (typeof source.ref !== 'string' || source.ref.length > 512))) {
|
|
17
|
+
throw new TypeError('origin summary needs valid claim identities and declared sources');
|
|
18
|
+
}
|
|
19
|
+
return { kind: source.kind, ...(Object.hasOwn(source, 'ref') ? { ref: source.ref } : {}) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Current declarations only: fault multiplicity cannot inflate claim counts. */
|
|
23
|
+
export function declaredOriginSummary(claims) {
|
|
24
|
+
const byKind = counts();
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
for (const claim of claims) {
|
|
27
|
+
const source = declaration(claim);
|
|
28
|
+
if (seen.has(claim.id)) throw new TypeError('duplicate declared claim identity');
|
|
29
|
+
seen.add(claim.id);
|
|
30
|
+
byKind[source.kind]++;
|
|
31
|
+
}
|
|
32
|
+
return { basis: 'declared', claims: { total: seen.size, byKind, mixed: 0 } };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Actual recorded projections only; no current-claims relabeling or filtering. */
|
|
36
|
+
export function recordedOriginSummary(records) {
|
|
37
|
+
const recordKinds = counts();
|
|
38
|
+
const claims = new Map();
|
|
39
|
+
for (const record of records) {
|
|
40
|
+
const source = declaration(record.claim);
|
|
41
|
+
recordKinds[source.kind]++;
|
|
42
|
+
const previous = claims.get(record.claim.id);
|
|
43
|
+
if (!previous) claims.set(record.claim.id, { source, mixed: false });
|
|
44
|
+
else if (!isDeepStrictEqual(previous.source, source)) previous.mixed = true;
|
|
45
|
+
}
|
|
46
|
+
const byKind = counts();
|
|
47
|
+
let mixed = 0;
|
|
48
|
+
for (const claim of claims.values()) {
|
|
49
|
+
if (claim.mixed) mixed++;
|
|
50
|
+
else byKind[claim.source.kind]++;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
basis: 'recorded', claims: { total: claims.size, byKind, mixed },
|
|
54
|
+
records: { total: records.length, byKind: recordKinds },
|
|
55
|
+
};
|
|
56
|
+
}
|