@holmes-lab/holmes-kit 0.1.7 → 0.1.8
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 +8 -0
- package/README.md +11 -7
- package/bin/holmes-mcp.js +0 -0
- package/dist/.build-id +1 -1
- package/dist/holmes/cli/agents.d.ts +50 -0
- package/dist/holmes/cli/ci-gate.d.ts +18 -0
- package/dist/holmes/cli/doctor.d.ts +44 -0
- package/dist/holmes/cli/gitignore-merge.d.ts +18 -0
- package/dist/holmes/cli/governed-precondition.d.ts +27 -0
- package/dist/holmes/cli/index.d.ts +14 -0
- package/dist/holmes/cli/index.js +23 -1
- package/dist/holmes/cli/init.d.ts +69 -0
- package/dist/holmes/cli/interactive-prompt.d.ts +9 -0
- package/dist/holmes/cli/playbook-skills.d.ts +129 -0
- package/dist/holmes/cli/roles-readme.d.ts +12 -0
- package/dist/holmes/cli/serve.d.ts +14 -0
- package/dist/holmes/cli/serve.js +70 -0
- package/dist/holmes/cli/settings-merge.d.ts +66 -0
- package/dist/holmes/config/config.d.ts +13 -0
- package/dist/holmes/context/bundler.d.ts +40 -0
- package/dist/holmes/context/render.d.ts +9 -0
- package/dist/holmes/context/tiers.d.ts +54 -0
- package/dist/holmes/context/tokens.d.ts +2 -0
- package/dist/holmes/cpg/ast-mutation.d.ts +31 -0
- package/dist/holmes/cpg/ast-mutation.js +126 -0
- package/dist/holmes/cpg/cpg-scanner.d.ts +69 -0
- package/dist/holmes/cpg/dynamic-cpg.d.ts +18 -0
- package/dist/holmes/cpg/dynamic-cpg.js +82 -0
- package/dist/holmes/cpg/hash-cache.d.ts +21 -0
- package/dist/holmes/cpg/language-parser-walk.d.ts +31 -0
- package/dist/holmes/cpg/language-parser-worker.d.ts +1 -0
- package/dist/holmes/cpg/language-parser.d.ts +116 -0
- package/dist/holmes/cpg/program-slicing.d.ts +21 -0
- package/dist/holmes/cpg/program-slicing.js +123 -0
- package/dist/holmes/cpg/scan-cache.d.ts +21 -0
- package/dist/holmes/cpg/source-path.d.ts +2 -0
- package/dist/holmes/cpg/test-files.d.ts +36 -0
- package/dist/holmes/governance/constitution-debt.d.ts +3 -0
- package/dist/holmes/governance/constitution-report.d.ts +23 -0
- package/dist/holmes/governance/constitution.d.ts +61 -0
- package/dist/holmes/governance/identity.d.ts +28 -0
- package/dist/holmes/governance/ledger-lock.d.ts +55 -0
- package/dist/holmes/governance/ledger-store.conformance.d.ts +11 -0
- package/dist/holmes/governance/ledger-store.d.ts +114 -0
- package/dist/holmes/governance/progress-ledger.d.ts +25 -0
- package/dist/holmes/governance/provenance-chain.d.ts +154 -0
- package/dist/holmes/governance/provenance-ledger.d.ts +102 -0
- package/dist/holmes/governance/provenance-schema.d.ts +21 -0
- package/dist/holmes/governance/replica-id.d.ts +20 -0
- package/dist/holmes/governance/role-policy.d.ts +52 -0
- package/dist/holmes/governance/trust-score.d.ts +32 -0
- package/dist/holmes/guardrail/anchors.d.ts +8 -0
- package/dist/holmes/guardrail/blind-spots.d.ts +32 -0
- package/dist/holmes/guardrail/decision-ledger.d.ts +29 -0
- package/dist/holmes/guardrail/executable-artifact.d.ts +1 -0
- package/dist/holmes/guardrail/governance-history.d.ts +15 -0
- package/dist/holmes/guardrail/phase.d.ts +27 -0
- package/dist/holmes/guardrail/risk-classifier.d.ts +52 -0
- package/dist/holmes/guardrail/risk-gate.d.ts +77 -0
- package/dist/holmes/guardrail/risk-types.d.ts +48 -0
- package/dist/holmes/guardrail/tspec-state.d.ts +71 -0
- package/dist/holmes/guardrail/write-target.d.ts +77 -0
- package/dist/holmes/hooks/adapters/antigravity.d.ts +42 -0
- package/dist/holmes/hooks/pre-tool-use.d.ts +144 -0
- package/dist/holmes/hooks/stop.d.ts +91 -0
- package/dist/holmes/mcp/basis.d.ts +100 -0
- package/dist/holmes/mcp/handlers.d.ts +482 -0
- package/dist/holmes/mcp/server.d.ts +2 -0
- package/dist/holmes/mcp/stdio-client.d.ts +51 -0
- package/dist/holmes/mcp/supervisor.d.ts +48 -0
- package/dist/holmes/mcp/tool-schemas.d.ts +12 -0
- package/dist/holmes/mcp/validate-args.d.ts +27 -0
- package/dist/holmes/messages/registry.d.ts +23 -0
- package/dist/holmes/project/baseline.d.ts +66 -0
- package/dist/holmes/project/change-source.d.ts +89 -0
- package/dist/holmes/project/ignore.d.ts +33 -0
- package/dist/holmes/project/root.d.ts +34 -0
- package/dist/holmes/reverse/anchor.d.ts +47 -0
- package/dist/holmes/reverse/cluster.d.ts +45 -0
- package/dist/holmes/reverse/draft.d.ts +33 -0
- package/dist/holmes/reverse/dynamic-wiring.d.ts +18 -0
- package/dist/holmes/reverse/scan.d.ts +103 -0
- package/dist/holmes/reverse/surface.d.ts +91 -0
- package/dist/holmes/reverse/test-map.d.ts +91 -0
- package/dist/holmes/review/coverage.d.ts +21 -0
- package/dist/holmes/review/findings.d.ts +49 -0
- package/dist/holmes/review/package.d.ts +26 -0
- package/dist/holmes/review/review-targets.d.ts +28 -0
- package/dist/holmes/review/scope.d.ts +13 -0
- package/dist/holmes/review/test-evidence.d.ts +31 -0
- package/dist/holmes/review/test-runner.d.ts +141 -0
- package/dist/holmes/rtm/dataflow-taint.d.ts +81 -0
- package/dist/holmes/rtm/gap-analyzer.d.ts +28 -0
- package/dist/holmes/rtm/git-changes.d.ts +33 -0
- package/dist/holmes/rtm/heatmap.d.ts +21 -0
- package/dist/holmes/rtm/incremental.d.ts +38 -0
- package/dist/holmes/rtm/localize.d.ts +36 -0
- package/dist/holmes/rtm/rtm-builder.d.ts +101 -0
- package/dist/holmes/rtm/rtm-check.d.ts +13 -0
- package/dist/holmes/rtm/rtm-graph.d.ts +88 -0
- package/dist/holmes/rtm/taint.d.ts +35 -0
- package/dist/holmes/rtm/test-scope.d.ts +85 -0
- package/dist/holmes/server/dashboard.d.ts +30 -0
- package/dist/holmes/server/dashboard.js +1758 -0
- package/dist/holmes/spec/approval-blockers.d.ts +62 -0
- package/dist/holmes/spec/breaking-change.d.ts +42 -0
- package/dist/holmes/spec/legacy-format.d.ts +93 -0
- package/dist/holmes/spec/spec-digest.d.ts +29 -0
- package/dist/holmes/spec/spec-parser.d.ts +12 -0
- package/dist/holmes/spec/spec-store.conformance.d.ts +10 -0
- package/dist/holmes/spec/spec-store.d.ts +130 -0
- package/dist/holmes/spec/spec-types.d.ts +150 -0
- package/dist/holmes/spec/validator.d.ts +65 -0
- package/package.json +2 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { LockOptions } from './ledger-lock';
|
|
2
|
+
/**
|
|
3
|
+
* N3 — Decision PROVENANCE chain (target-architecture §7-N N3): "누가·언제·왜"의 완전 인과 재구성.
|
|
4
|
+
*
|
|
5
|
+
* Upgrades the L4 ledgers from plain append-only JSONL to a TAMPER-EVIDENT HASH CHAIN: every event
|
|
6
|
+
* carries `prevHash` and `hash = sha256(prevHash | canonical(event))`, so any rewrite, deletion, or
|
|
7
|
+
* reordering of history breaks verification at the exact seam. Honest naming: this is tamper-EVIDENT
|
|
8
|
+
* (a forger cannot alter past events without detection), not a cryptographic SIGNATURE — there is no
|
|
9
|
+
* key infrastructure, so a forger who rewrites the ENTIRE chain from the tampered point forward would
|
|
10
|
+
* pass verification; the defense against that is the write-gate on `.ax/ledger` (HIGH-3 hardening)
|
|
11
|
+
* plus out-of-band copies. Full signing (HMAC/asymmetric) is a documented future step.
|
|
12
|
+
*
|
|
13
|
+
* Each event is a Decision Event node: consumed inputs (evidence refs), rationale, actor, timestamp,
|
|
14
|
+
* and the authorization that unlocked it — the GAIE "Decision Event" concept realized on the RTM side.
|
|
15
|
+
*
|
|
16
|
+
* Pure half: canonicalize / computeHash / chainNext / verifyChain (caller supplies ts; no clock).
|
|
17
|
+
* I/O half: ProvenanceChain over a JSONL file, ENOENT -> empty, other read errors THROW (silently
|
|
18
|
+
* returning [] would erase the audit trail this exists to guarantee — same policy as DecisionLedger).
|
|
19
|
+
*/
|
|
20
|
+
export interface ProvenanceEvent {
|
|
21
|
+
seq: number;
|
|
22
|
+
ts: string;
|
|
23
|
+
actor: string;
|
|
24
|
+
kind: string;
|
|
25
|
+
summary: string;
|
|
26
|
+
inputs?: string[];
|
|
27
|
+
rationale?: string;
|
|
28
|
+
authorization?: string;
|
|
29
|
+
prevHash: string;
|
|
30
|
+
hash: string;
|
|
31
|
+
}
|
|
32
|
+
type ProvenanceBody = Omit<ProvenanceEvent, 'prevHash' | 'hash'>;
|
|
33
|
+
export declare const GENESIS = "GENESIS";
|
|
34
|
+
/** Ordered-key serialization so identical bodies always produce identical bytes (hash stability). */
|
|
35
|
+
export declare function canonicalize(b: ProvenanceBody): string;
|
|
36
|
+
/**
|
|
37
|
+
* Event hash. WITHOUT a key: plain sha256 chain (tamper-EVIDENT — a full rewrite from point k still
|
|
38
|
+
* passes, the documented no-key limit). WITH an out-of-band key (HOLMES_LEDGER_KEY env, same channel
|
|
39
|
+
* discipline as HOLMES_APPROVAL): HMAC-SHA256 — a forger without the key cannot recompute ANY link,
|
|
40
|
+
* so even the full-rewrite attack fails verification. Key presence upgrades the guarantee from
|
|
41
|
+
* tamper-evident to tamper-PROOF-against-keyless-forgers; absence degrades gracefully to the chain.
|
|
42
|
+
*/
|
|
43
|
+
export declare function computeHash(prevHash: string, body: ProvenanceBody, key?: string): string;
|
|
44
|
+
/** Build the next chained event from the current chain tail (pure). */
|
|
45
|
+
export declare function chainNext(chain: ProvenanceEvent[], body: Omit<ProvenanceBody, 'seq'>, key?: string): ProvenanceEvent;
|
|
46
|
+
/**
|
|
47
|
+
* Verify the whole chain: recomputed hashes, prevHash linkage, contiguous seq, AND no unknown fields.
|
|
48
|
+
* The unknown-field check closes the canonicalization-gap attack (adversarial review #2): canonicalize
|
|
49
|
+
* hashes only the known fields, so a tamperer could smuggle forged data in an EXTRA field that the
|
|
50
|
+
* hash ignores but a consumer might read — any field outside the schema now fails verification.
|
|
51
|
+
* Returns the FIRST broken seam so remediation is addressable. An empty chain is trivially valid.
|
|
52
|
+
*/
|
|
53
|
+
export declare function verifyChain(chain: ProvenanceEvent[], key?: string): {
|
|
54
|
+
ok: boolean;
|
|
55
|
+
brokenAt?: number;
|
|
56
|
+
detail?: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Reference an approval credential WITHOUT persisting it: a short sha256 fingerprint identifies which
|
|
60
|
+
* token authorized an action while the ledger never stores the replayable secret (adversarial review
|
|
61
|
+
* #1 — the raw token in an audit file meant any local reader could harvest live gate-unlock
|
|
62
|
+
* credentials).
|
|
63
|
+
*/
|
|
64
|
+
export declare function authorizationRef(actor: string, token: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Redact a tool's target before it enters the audit trail.
|
|
67
|
+
*
|
|
68
|
+
* The chain used to store the raw command text. Audited on this repository's own ledger: 29 records,
|
|
69
|
+
* 15 carrying absolute home paths and 6 matching secret-ish patterns. The hazard is structural, not
|
|
70
|
+
* incidental — the gate DENIES commands like `echo $HOLMES_APPROVAL` and `HOLMES_LEDGER_KEY=…`, and
|
|
71
|
+
* a denial is exactly what gets recorded, so the ledger is the one place guaranteed to collect the
|
|
72
|
+
* secrets the gate exists to protect. Committing it would publish them irreversibly.
|
|
73
|
+
*
|
|
74
|
+
* Same principle `authorizationRef` already applies to tokens: fingerprint, never store.
|
|
75
|
+
*
|
|
76
|
+
* - A FILE PATH is kept, because "which file was touched" is the audit question, but normalised to
|
|
77
|
+
* repo-relative; anything outside the repository becomes a placeholder rather than a host path.
|
|
78
|
+
* - A COMMAND is never kept. Its digest is recorded instead, so identical attempts remain
|
|
79
|
+
* correlatable across records without the text being recoverable. Nothing is lost for audit: the
|
|
80
|
+
* event's `summary` already carries the human-readable classification ("hard-hitl risk: git push
|
|
81
|
+
* --force rewrites remote history"), which is what a reader actually needs.
|
|
82
|
+
*/
|
|
83
|
+
export declare function redactTarget(kind: 'path' | 'command', value: string, repoRoot?: string): string;
|
|
84
|
+
export declare function blankNonce(nonce: unknown): boolean;
|
|
85
|
+
/** Declared at all — the operand for "does this grant claim single use?" (falsy values included). */
|
|
86
|
+
export declare function nonceDeclared(nonce: unknown): boolean;
|
|
87
|
+
export declare function nonceFingerprint(nonce: string): string;
|
|
88
|
+
export declare function approvalMarkers(approval: {
|
|
89
|
+
scope?: {
|
|
90
|
+
kind: string;
|
|
91
|
+
pattern: string;
|
|
92
|
+
}[];
|
|
93
|
+
expires?: string;
|
|
94
|
+
nonce?: string;
|
|
95
|
+
} | undefined): string;
|
|
96
|
+
export declare class ProvenanceChain {
|
|
97
|
+
private readonly file;
|
|
98
|
+
constructor(file: string);
|
|
99
|
+
/** Load all events. ENOENT -> []; any other error throws (audit trail must not silently vanish). */
|
|
100
|
+
load(): ProvenanceEvent[];
|
|
101
|
+
/** Append a new event chained onto the CURRENT on-disk tail. Returns the appended event.
|
|
102
|
+
* The signing key comes from HOLMES_LEDGER_KEY (out-of-band env, never tool-settable in-session).
|
|
103
|
+
*
|
|
104
|
+
* @implements A-SPEC-141
|
|
105
|
+
* The read-and-write span runs under exclusion. Measured without it: two processes read the same
|
|
106
|
+
* tail, both wrote events claiming the same `seq`, and `verify()` then reported `ok:false` —
|
|
107
|
+
* tampering that never happened, 17 times out of 20. */
|
|
108
|
+
append(body: Omit<ProvenanceBody, 'seq'>): ProvenanceEvent;
|
|
109
|
+
/**
|
|
110
|
+
* Append assuming exclusion is ALREADY held. Only for callers running inside `withLedgerLock` on
|
|
111
|
+
* this same file — `consumeNonceExclusively` needs the read and the write in one hold, and calling
|
|
112
|
+
* `append()` from there would deadlock against its own budget.
|
|
113
|
+
*/
|
|
114
|
+
appendInsideLock(body: Omit<ProvenanceBody, 'seq'>): ProvenanceEvent;
|
|
115
|
+
/** The append itself, assuming exclusion is already held. Only callers INSIDE a hold may use it. */
|
|
116
|
+
private appendUnlocked;
|
|
117
|
+
/**
|
|
118
|
+
* Records a broken stale hold into the chain itself. Bound so it can be handed to the lock layer,
|
|
119
|
+
* which must not import this module (the lock is the lower layer).
|
|
120
|
+
*
|
|
121
|
+
* Written WITHOUT re-entering the lock: this fires from inside the acquire loop, where the hold has
|
|
122
|
+
* just been released and not yet re-taken, so calling `append()` here would deadlock against the
|
|
123
|
+
* budget. The event may therefore race a concurrent writer, which is acceptable for a diagnostic —
|
|
124
|
+
* a missing note is better than a wedged harness, and `verify()` still reports any resulting seam.
|
|
125
|
+
*/
|
|
126
|
+
staleBreakRecorder(): NonNullable<LockOptions['onStaleBreak']>;
|
|
127
|
+
private recordStaleBreak;
|
|
128
|
+
verify(): {
|
|
129
|
+
ok: boolean;
|
|
130
|
+
brokenAt?: number;
|
|
131
|
+
detail?: string;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Default chain location, beside the other L4 ledgers (protected surface). */
|
|
135
|
+
export declare const PROVENANCE_FILE: string;
|
|
136
|
+
/**
|
|
137
|
+
* Spend a single-use approval, atomically. Returns `true` iff THIS caller spent it.
|
|
138
|
+
*
|
|
139
|
+
* @implements A-SPEC-141
|
|
140
|
+
* THIS is the operation REQ-141 exists for. The gate used to ask `isNonceConsumed(...)` and only
|
|
141
|
+
* afterwards append the consumption record, so two agents both observed "not consumed" and both
|
|
142
|
+
* proceeded: measured, a single-use approval for a destructive command was honoured TWICE in 20 of
|
|
143
|
+
* 20 barrier-synchronized trials. Locking the append alone would not have helped — the unguarded
|
|
144
|
+
* span is the READ followed by the WRITE, so both must live inside one hold.
|
|
145
|
+
*
|
|
146
|
+
* A loser writes NOTHING. Recording a consumption the caller was refused would put a second
|
|
147
|
+
* `nonce-consumed` event in the audit trail for an action that never happened.
|
|
148
|
+
*
|
|
149
|
+
* Throws `LedgerLockError` when exclusion cannot be obtained. The direction to fail is the CALLER's
|
|
150
|
+
* decision, not this function's: an authorization gate must deny, while a record-keeping append must
|
|
151
|
+
* not flip a decision that was already made.
|
|
152
|
+
*/
|
|
153
|
+
export declare function consumeNonceExclusively(nonce: string, ledgerFile: string, body: Omit<ProvenanceBody, 'seq'>, opts?: LockOptions): boolean;
|
|
154
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ProvenanceChain, ProvenanceEvent } from './provenance-chain';
|
|
2
|
+
/**
|
|
3
|
+
* The ledger as a SET of per-replica chains, read together and written one at a time.
|
|
4
|
+
*
|
|
5
|
+
* WHY, measured: ADR-012 made `provenance.jsonl` a shared SSOT tracked in git, explicitly so a
|
|
6
|
+
* colleague who clones can answer "who decided this, when, and why". That decision opens a use case
|
|
7
|
+
* the format cannot survive — two machines appending to one chain and merging produces
|
|
8
|
+
* `{"ok":false,"detail":"seq gap: expected 2, found 1"}`. Tamper detection fires when nobody
|
|
9
|
+
* tampered, and because `isBaselineTrusted` requires an intact chain, baseline trust collapses with
|
|
10
|
+
* it. REQ-141 solved the same shape WITHIN one machine with a lock; across machines there is no
|
|
11
|
+
* shared filesystem to lock.
|
|
12
|
+
*
|
|
13
|
+
* The primitive is deliberately untouched. `ProvenanceChain` remains "one file, one chain", so the
|
|
14
|
+
* legacy `provenance.jsonl` keeps loading and verifying with no migration at all — this change obeys
|
|
15
|
+
* the ADR-013 tier-① rule it was written under — and REQ-141's per-file lock keeps working unchanged.
|
|
16
|
+
*
|
|
17
|
+
* WHAT IS LOST: the globally monotonic `seq`, and with it a total order proven by hashes. After the
|
|
18
|
+
* split that proof holds only within a replica.
|
|
19
|
+
*
|
|
20
|
+
* WHAT REPLACES IT: git. These files are tracked (ADR-012), any deletion or rewrite shows up in
|
|
21
|
+
* history, and a git commit is itself a hash chain. The cross-replica anchor does not disappear; it
|
|
22
|
+
* moves one layer up. That is why splitting is not the same as giving up detection.
|
|
23
|
+
*/
|
|
24
|
+
export interface LedgerEvent extends ProvenanceEvent {
|
|
25
|
+
/** Which chain this came from; `null` for the pre-split legacy file. */
|
|
26
|
+
replicaId: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface LedgerVerifyResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
/** Per-chain failures, naming the replica so a report is actionable rather than merely alarming. */
|
|
31
|
+
broken: {
|
|
32
|
+
replicaId: string | null;
|
|
33
|
+
file: string;
|
|
34
|
+
detail?: string;
|
|
35
|
+
brokenAt?: number;
|
|
36
|
+
}[];
|
|
37
|
+
}
|
|
38
|
+
export declare class ProvenanceLedger {
|
|
39
|
+
private readonly dir;
|
|
40
|
+
/**
|
|
41
|
+
* Resolved LAZILY. Constructing a reader must not write anything, and `resolveReplicaId`
|
|
42
|
+
* persists `.ax/state/replica-id` on first use — doing that from `at()` made every read create
|
|
43
|
+
* a `.ax/state/` directory, which changed what `resolveProjectRoot` found in test fixtures and
|
|
44
|
+
* broke ten unrelated tests. A read that leaves a footprint is a defect regardless of what it
|
|
45
|
+
* happens to break.
|
|
46
|
+
*/
|
|
47
|
+
private readonly replicaIdFn;
|
|
48
|
+
private indexCache?;
|
|
49
|
+
private constructor();
|
|
50
|
+
private cachedId;
|
|
51
|
+
private get replicaId();
|
|
52
|
+
/**
|
|
53
|
+
* `replicaId` is normally resolved from the machine; tests and CI pass it explicitly. The project
|
|
54
|
+
* root is taken to be the ledger directory's grandparent (`<root>/.ax/ledger`), which is where
|
|
55
|
+
* `.ax/state/replica-id` lives.
|
|
56
|
+
*/
|
|
57
|
+
static at(dir: string, replicaId?: string): ProvenanceLedger;
|
|
58
|
+
/**
|
|
59
|
+
* This writer's replica id WITHOUT persisting it.
|
|
60
|
+
*
|
|
61
|
+
* @implements A-SPEC-150
|
|
62
|
+
* Asking "who am I" is a read, and the ledger port's conformance requires a read to leave no
|
|
63
|
+
* trace. `resolveReplicaId` persists by default because the value has to be stable for a WRITER;
|
|
64
|
+
* a reader gets the same answer with `persist: false`. Merely calling this used to create
|
|
65
|
+
* `.ax/state/`, which is the REQ-148 footprint defect returning through a new door.
|
|
66
|
+
*/
|
|
67
|
+
replicaIdOf(): string;
|
|
68
|
+
/** Every chain file present, legacy first. `replicaId` is null for the legacy chain. */
|
|
69
|
+
chains(): {
|
|
70
|
+
replicaId: string | null;
|
|
71
|
+
file: string;
|
|
72
|
+
}[];
|
|
73
|
+
/** The file this machine appends to. Never the legacy chain — see `append`. */
|
|
74
|
+
currentFile(): string;
|
|
75
|
+
private computeMtimeKey;
|
|
76
|
+
private cloneEvents;
|
|
77
|
+
/**
|
|
78
|
+
* Every event across every chain, ordered by `(ts, replicaId, seq)`.
|
|
79
|
+
*
|
|
80
|
+
* Deterministic, and deliberately NOT a total causal order: two events on different replicas have
|
|
81
|
+
* no causal relation, and their timestamps differ by whatever the machines' clocks differ by. The
|
|
82
|
+
* ordering exists so output is stable and readable, not so it can be read as "this happened first".
|
|
83
|
+
*/
|
|
84
|
+
loadAll(): LedgerEvent[];
|
|
85
|
+
/** Each chain verified on its own; one bad chain fails the whole ledger, and is named. */
|
|
86
|
+
verifyAll(): LedgerVerifyResult;
|
|
87
|
+
/**
|
|
88
|
+
* Append to THIS replica's chain.
|
|
89
|
+
*
|
|
90
|
+
* Never to the legacy file: continuing to write there would put two machines back on one chain,
|
|
91
|
+
* which is the defect this class exists to remove.
|
|
92
|
+
*/
|
|
93
|
+
append(body: Parameters<ProvenanceChain['append']>[0]): ProvenanceEvent;
|
|
94
|
+
/**
|
|
95
|
+
* Has this nonce been spent on ANY replica?
|
|
96
|
+
*
|
|
97
|
+
* The trap the split creates: judging from one chain alone lets a single-use approval be spent once
|
|
98
|
+
* per replica, which would resurrect REQ-141's double-spend along a new axis. The question is only
|
|
99
|
+
* meaningful across the whole set.
|
|
100
|
+
*/
|
|
101
|
+
isNonceConsumed(nonce: string): boolean;
|
|
102
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The provenance ledger's schema version, read from the FILE NAME rather than from the records.
|
|
3
|
+
*
|
|
4
|
+
* WHY the filename and not a field, measured rather than chosen: adding `v` to a record fails
|
|
5
|
+
* verification with `unknown field(s) outside the hashed schema: v (smuggled data)` — the
|
|
6
|
+
* anti-smuggling check an earlier adversarial review added, working exactly as intended. The two
|
|
7
|
+
* ways around it are both worse than the problem. Allowing an exempt field creates one field a
|
|
8
|
+
* forger can write freely, inside the artefact whose whole purpose is tamper evidence. Adding the
|
|
9
|
+
* field to `canonicalize` changes the canonical form of every record, so all 1804 existing entries
|
|
10
|
+
* stop matching their own hashes — a compatibility rule that destroys the tier-① artefact it was
|
|
11
|
+
* written to protect.
|
|
12
|
+
*
|
|
13
|
+
* That leaves the name. It is not a workaround: the replica split is about to rename these files
|
|
14
|
+
* anyway, so the name is already the thing that changes when the schema changes, and reading it
|
|
15
|
+
* costs nothing.
|
|
16
|
+
*
|
|
17
|
+
* - `provenance.jsonl` → 1 (single global chain, contiguous seq)
|
|
18
|
+
* - `provenance.<replicaId>.jsonl` → 2 (per-replica chain, seq local to the replica)
|
|
19
|
+
*/
|
|
20
|
+
export type ProvenanceSchema = 1 | 2;
|
|
21
|
+
export declare function provenanceSchemaOf(fileName: string): ProvenanceSchema;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reduce a raw name to something safe to embed in `provenance.<id>.jsonl`.
|
|
3
|
+
*
|
|
4
|
+
* DOTS ARE THE POINT. macOS hostnames end in `.local`, and the schema rule reads
|
|
5
|
+
* `provenance.<segment>.jsonl` with `<segment>` containing no dot — an unsanitised
|
|
6
|
+
* `SungNamui-MacStudio.local` would produce `provenance.SungNamui-MacStudio.local.jsonl`, which the
|
|
7
|
+
* schema check would not recognise as a replica chain at all. Two rules disagreeing about the same
|
|
8
|
+
* file is worse than either rule being wrong.
|
|
9
|
+
*/
|
|
10
|
+
export declare function sanitizeReplicaId(raw: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve this machine's replica id: explicit env, then the stored value, then the hostname.
|
|
13
|
+
*
|
|
14
|
+
* The env override exists for CI and for tests, where the hostname is neither stable nor meaningful.
|
|
15
|
+
* Writing the generated value down is what makes the identity stable — an id derived fresh each run
|
|
16
|
+
* would scatter one machine's history across several chains.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveReplicaId(root: string, env?: NodeJS.ProcessEnv, opts?: {
|
|
19
|
+
persist?: boolean;
|
|
20
|
+
}): string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Identity } from './identity';
|
|
2
|
+
/**
|
|
3
|
+
* What each role may do, as project-owned governance config.
|
|
4
|
+
*
|
|
5
|
+
* The allow-list entries are the action names `classifyAction` already produces (`WRITE_CODE`,
|
|
6
|
+
* `WRITE_TEST`, …). Inventing a separate vocabulary would let what the gate decides and what a role
|
|
7
|
+
* claims drift apart, and the drift would be invisible until someone was wrongly allowed.
|
|
8
|
+
*
|
|
9
|
+
* OPT-IN. A project with no `.ax/roles/` has no policy and the gate behaves exactly as before —
|
|
10
|
+
* making roles mandatory would deny every existing project at once, which is the bricking REQ-146
|
|
11
|
+
* measured when it tried to make a spec field required.
|
|
12
|
+
*
|
|
13
|
+
* The definition lives under a PROTECTED path. Blocking role claims while leaving the policy
|
|
14
|
+
* writable would be one lock on a door with two entrances: an agent that cannot change its role
|
|
15
|
+
* would simply change what that role is allowed to do.
|
|
16
|
+
*/
|
|
17
|
+
export interface RolePolicy {
|
|
18
|
+
roles: Record<string, {
|
|
19
|
+
allow: string[];
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Which role an UNSPECIFIED agent is treated as. Declared by the project, never assumed by code.
|
|
23
|
+
*
|
|
24
|
+
* That distinction is the whole security argument. If the code defaulted to a full-capability role
|
|
25
|
+
* whenever `HOLMES_ROLE` was absent, every restriction would be bypassable by unsetting one
|
|
26
|
+
* environment variable — the rule that binds a Reviewer would become "don't set it". Because this
|
|
27
|
+
* lives in `.ax/roles/`, which is a PROTECTED path, an agent cannot write it either.
|
|
28
|
+
*
|
|
29
|
+
* Omitting it keeps the fail-closed behaviour: unspecified is refused.
|
|
30
|
+
*/
|
|
31
|
+
default?: string;
|
|
32
|
+
}
|
|
33
|
+
/** `null` when the project has not opted in. A PRESENT-but-broken policy is never null — see below. */
|
|
34
|
+
export declare function loadRolePolicy(root: string): RolePolicy | null;
|
|
35
|
+
/** What `doctor` reports. Three states, because "no policy" and "guidance present but off" differ. */
|
|
36
|
+
export interface RolePolicyStatus {
|
|
37
|
+
state: 'absent' | 'inactive' | 'active';
|
|
38
|
+
roles: string[];
|
|
39
|
+
default?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Describes the project's role configuration WITHOUT judging it. Not having roles on is not a
|
|
43
|
+
* defect; reporting it as one would teach operators to ignore doctor, which costs more than it saves.
|
|
44
|
+
*/
|
|
45
|
+
export declare function rolePolicyStatus(root: string): RolePolicyStatus;
|
|
46
|
+
/**
|
|
47
|
+
* Whether this identity may perform this action. Returns the refusal reason, or `null` to allow.
|
|
48
|
+
*
|
|
49
|
+
* Fail-closed once a policy exists: an absent role, an unregistered role, and a role without the
|
|
50
|
+
* action are all refusals. Before a policy exists there is no opinion at all.
|
|
51
|
+
*/
|
|
52
|
+
export declare function roleCheck(action: string, identity: Identity, policy: RolePolicy | null): string | null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L5 — quantitative TRUST SCORE → automation level (target-architecture §7 L5).
|
|
3
|
+
*
|
|
4
|
+
* bkit's trust-score pattern, deterministic: recent governed-work metrics fold into a 0..1 score and
|
|
5
|
+
* a discrete automation LEVEL that callers (risk gate, orchestrators) can use to graduate autonomy —
|
|
6
|
+
* high sustained trust earns wider auto-approval; faults/regressions/critical findings walk it back.
|
|
7
|
+
*
|
|
8
|
+
* L0 observe — read/analyze only
|
|
9
|
+
* L1 propose — may draft changes, human applies
|
|
10
|
+
* L2 gated — may apply changes behind every gate (default posture)
|
|
11
|
+
* L3 trusted — routine (auto-level) actions skip confirm-level friction
|
|
12
|
+
* L4 autonomous— may batch routine work; hard-hitl still ALWAYS gates (never waived — ART-5)
|
|
13
|
+
*
|
|
14
|
+
* Pure and clock-free: the caller aggregates the window (e.g. from the L4 ledgers) into TrustMetrics.
|
|
15
|
+
* Conservative by construction: zero history -> L2 default-gated, never trusted; any fault or
|
|
16
|
+
* regression caps the level regardless of volume (a burst of green cannot buy back a fresh fault).
|
|
17
|
+
*/
|
|
18
|
+
export interface TrustMetrics {
|
|
19
|
+
slicesCompleted: number;
|
|
20
|
+
suiteGreenRate: number;
|
|
21
|
+
guardrailDenials: number;
|
|
22
|
+
faults: number;
|
|
23
|
+
regressions: number;
|
|
24
|
+
criticalFindings: number;
|
|
25
|
+
}
|
|
26
|
+
export type AutomationLevel = 'L0' | 'L1' | 'L2' | 'L3' | 'L4';
|
|
27
|
+
export interface TrustAssessment {
|
|
28
|
+
score: number;
|
|
29
|
+
level: AutomationLevel;
|
|
30
|
+
rationale: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function computeTrustScore(m: TrustMetrics): TrustAssessment;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function claimedAnchors(content: string): string[];
|
|
2
|
+
/**
|
|
3
|
+
* The anchors this write ADDS, relative to what the file already had.
|
|
4
|
+
*
|
|
5
|
+
* Only the additions are judged. Re-judging anchors already in the file would block every edit to a
|
|
6
|
+
* file whose spec later moved back to draft — including the edits needed to fix exactly that.
|
|
7
|
+
*/
|
|
8
|
+
export declare function newlyClaimed(content: string, previous: string | undefined): string[];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the guardrail CANNOT see, declared in code rather than prose.
|
|
3
|
+
*
|
|
4
|
+
* The guardrail inspects the command STRING. Anything whose behaviour lives elsewhere — a script
|
|
5
|
+
* file, a variable, stdin — is opaque to it, and no amount of pattern work changes that: what
|
|
6
|
+
* `node ./x.js` does is not statically decidable. Measured 2026-08-08: every entry below reached
|
|
7
|
+
* `allow`.
|
|
8
|
+
*
|
|
9
|
+
* Declared here, not in a document, because a test can read this and assert that each example really
|
|
10
|
+
* is allowed. A limit written in prose drifts silently from the code; twice this session a claim in
|
|
11
|
+
* a comment turned out to be false when measured. Keeping the claim next to an executable check
|
|
12
|
+
* means whoever later closes one of these has to come here and rewrite it.
|
|
13
|
+
*
|
|
14
|
+
* REQ-167 already closed the half that mattered most: WRITING a script now needs an approved spec,
|
|
15
|
+
* so `x.sh` followed by `bash ./x.sh` no longer completes on its own. What remains is running a
|
|
16
|
+
* script that was ALREADY in the repository — code a human committed.
|
|
17
|
+
*/
|
|
18
|
+
export interface BlindSpot {
|
|
19
|
+
/** A command the gate allows, verbatim, so a test can check the claim. */
|
|
20
|
+
example: string;
|
|
21
|
+
/** Why it is opaque. */
|
|
22
|
+
why: string;
|
|
23
|
+
}
|
|
24
|
+
export declare const GATE_BLIND_SPOTS: readonly BlindSpot[];
|
|
25
|
+
/**
|
|
26
|
+
* One line for `doctor`.
|
|
27
|
+
*
|
|
28
|
+
* Deliberately neither "the gate stops everything" nor "the gate stops nothing". The first gets
|
|
29
|
+
* someone hurt; the second makes a reader distrust the parts that do work — and after REQ-167 those
|
|
30
|
+
* parts include creating the script in the first place.
|
|
31
|
+
*/
|
|
32
|
+
export declare function blindSpotSummary(): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { RiskAction, RiskLevel } from './risk-types';
|
|
2
|
+
export interface DecisionEvent {
|
|
3
|
+
ts: string;
|
|
4
|
+
actor: string;
|
|
5
|
+
action: RiskAction;
|
|
6
|
+
level: RiskLevel;
|
|
7
|
+
decision: 'auto' | 'approved' | 'proceeded-unconfirmed' | 'blocked' | 'rejected';
|
|
8
|
+
rationale: string;
|
|
9
|
+
reasons: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Append-only, provenance audit trail for riskGate decisions. Each call to
|
|
13
|
+
* `record` appends one JSON line per event to a `.jsonl` file -- never
|
|
14
|
+
* truncated, never rewritten -- so the file itself is the durable evidence
|
|
15
|
+
* trail.
|
|
16
|
+
*
|
|
17
|
+
* Timestamps and ids are caller-supplied: this ledger has no clock, so
|
|
18
|
+
* recording is fully deterministic given its inputs. Ledger I/O is
|
|
19
|
+
* deliberately separate from riskGate's decision -- a write failure here
|
|
20
|
+
* must never change a gate verdict that was already computed.
|
|
21
|
+
*/
|
|
22
|
+
export declare class DecisionLedger {
|
|
23
|
+
private readonly path;
|
|
24
|
+
constructor(path: string);
|
|
25
|
+
record(events: DecisionEvent[]): void;
|
|
26
|
+
list(): DecisionEvent[];
|
|
27
|
+
/** Ordered-object serialization so identical events always produce identical bytes. */
|
|
28
|
+
private static serialize;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isExecutableArtifact(relPath: string, content?: string): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two ways out, and why the project is being judged governed at all.
|
|
3
|
+
*
|
|
4
|
+
* Shared by both hooks so they cannot describe the same situation differently. Naming only the
|
|
5
|
+
* restore path would be no guidance to a user whose actual intent is to stop being governed — they
|
|
6
|
+
* would keep deleting things until it stopped.
|
|
7
|
+
*/
|
|
8
|
+
export declare const GOVERNANCE_LOST_HINT: string;
|
|
9
|
+
/**
|
|
10
|
+
* True if any provenance replica records a spec approval.
|
|
11
|
+
*
|
|
12
|
+
* Fails toward `true` on anything unreadable: if "cannot read" meant "never governed", corrupting a
|
|
13
|
+
* ledger file would be a way to switch the gate off — worse than the hole this closes.
|
|
14
|
+
*/
|
|
15
|
+
export declare function hasGovernanceHistory(root: string): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Spec } from '../spec/spec-parser';
|
|
2
|
+
export declare const ACTIONS: readonly ["AUTHOR_REQ", "AUTHOR_HSPEC", "AUTHOR_ASPEC", "AUTHOR_CSPEC", "AUTHOR_TSPEC", "WRITE_TEST", "WRITE_CODE"];
|
|
3
|
+
export type Action = (typeof ACTIONS)[number];
|
|
4
|
+
export interface CheckResult {
|
|
5
|
+
decision: 'allow' | 'deny';
|
|
6
|
+
phase?: string;
|
|
7
|
+
missing?: string[];
|
|
8
|
+
remediation?: {
|
|
9
|
+
message: string;
|
|
10
|
+
next_action: string;
|
|
11
|
+
discipline: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Classify a write target. `p` must be PROJECT-RELATIVE (POSIX or Windows separators both fine).
|
|
16
|
+
*
|
|
17
|
+
* Relative matters for the test rules: several of them key on a directory segment, so an absolute
|
|
18
|
+
* path would let a project checked out under `/…/test/` classify its production code as tests —
|
|
19
|
+
* DOWNGRADING the gate, since WRITE_TEST does not require an approved T-SPEC. Callers that hold an
|
|
20
|
+
* absolute path make it relative first (see evaluateHook).
|
|
21
|
+
*/
|
|
22
|
+
export declare function classifyAction(p: string, content?: string): Action | null;
|
|
23
|
+
export declare function phaseCheck(action: Action, ctx: {
|
|
24
|
+
specs: Spec[];
|
|
25
|
+
targetParentReqId?: string;
|
|
26
|
+
targetAspecId?: string;
|
|
27
|
+
}): CheckResult;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { AxisVerdict, RiskAction, RiskAssessment, RiskThresholds } from './risk-types';
|
|
2
|
+
export declare function rmCommandTargets(cmd: string): string[];
|
|
3
|
+
/**
|
|
4
|
+
* Neutralise `.ax` paths that belong to ANOTHER project before the protected-path rules run.
|
|
5
|
+
*
|
|
6
|
+
* `.ax/specs` is this project's governance directory, and deleting it must stay non-bypassable. But
|
|
7
|
+
* the rules match the substring anywhere in a command, so `rm /other/project/.ax/cpg_cache/x.json`
|
|
8
|
+
* was gated too — found by dogfooding while cleaning up a scan cache left in a DIFFERENT repository,
|
|
9
|
+
* which is exactly what a user juggling several projects does every day. The governance claim only
|
|
10
|
+
* extends to this project's own `.ax`.
|
|
11
|
+
*
|
|
12
|
+
* Only ABSOLUTE paths outside the project root are masked. A relative `.ax/...` resolves against the
|
|
13
|
+
* project and stays protected, so nothing is weakened for the case the rule exists to cover. With no
|
|
14
|
+
* projectRoot supplied the command is returned unchanged — unknown scope stays conservative.
|
|
15
|
+
*/
|
|
16
|
+
export declare function scopeAxToProject(cmd: string, projectRoot?: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Normalize a command string for protected-path matching: collapse `//` runs and `./` segments so
|
|
19
|
+
* `./.ax//ledger/x` matches the same rules as `.ax/ledger/x` (adversarial probe: those forms slipped
|
|
20
|
+
* every protected-path regex). Also flattens a leading `cd <dir> &&` so a cd-relative write
|
|
21
|
+
* (`cd .ax/ledger && cat > provenance.jsonl`) is seen against its real directory.
|
|
22
|
+
*/
|
|
23
|
+
export declare function normalizeCommandPaths(cmd: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* @implements A-SPEC-125.1
|
|
26
|
+
* Reversibility axis: can the action's effect be undone? Conservative default —
|
|
27
|
+
* unknown kinds and ambiguous (tracked===undefined) states never resolve to 'auto'.
|
|
28
|
+
*/
|
|
29
|
+
export declare function classifyReversibility(action: RiskAction): AxisVerdict;
|
|
30
|
+
/**
|
|
31
|
+
* @implements A-SPEC-125.1
|
|
32
|
+
* Architecture axis: does this action reverse a previously accepted architectural decision?
|
|
33
|
+
*/
|
|
34
|
+
export declare function classifyArchitectureReversal(action: RiskAction): AxisVerdict;
|
|
35
|
+
/**
|
|
36
|
+
* @implements A-SPEC-125.1
|
|
37
|
+
* Secret axis: does the staged content contain a credential-shaped string?
|
|
38
|
+
*/
|
|
39
|
+
export declare function classifySecret(action: RiskAction): AxisVerdict;
|
|
40
|
+
/**
|
|
41
|
+
* @implements A-SPEC-125.3
|
|
42
|
+
* Blast-radius axis: how far does this change's impact ripple through the spec graph?
|
|
43
|
+
* The graph impact signal (`action.blastRadius`) is INJECTED by the caller — this function
|
|
44
|
+
* remains pure, performing no graph traversal or I/O of its own.
|
|
45
|
+
*/
|
|
46
|
+
export declare function classifyBlastRadius(action: RiskAction, cfg?: RiskThresholds): AxisVerdict;
|
|
47
|
+
/**
|
|
48
|
+
* @implements A-SPEC-125.1
|
|
49
|
+
* Runs all four axes and folds them into a single overall risk assessment.
|
|
50
|
+
* Pure function: no I/O, no clocks, no randomness — same input always yields the same output.
|
|
51
|
+
*/
|
|
52
|
+
export declare function assessRisk(action: RiskAction, cfg?: RiskThresholds): RiskAssessment;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { RiskAssessment, RiskLevel } from './risk-types';
|
|
2
|
+
export type Enforcement = 'block' | 'warn' | 'off';
|
|
3
|
+
export interface Approval {
|
|
4
|
+
actor: string;
|
|
5
|
+
token: string;
|
|
6
|
+
rationale: string;
|
|
7
|
+
/** If present, the approval authorizes ONLY actions matching some entry (deny-by-default within). */
|
|
8
|
+
scope?: {
|
|
9
|
+
kind: string;
|
|
10
|
+
pattern: string;
|
|
11
|
+
}[];
|
|
12
|
+
/** ISO 8601. If present, the approval is dead once the action timestamp passes it. */
|
|
13
|
+
expires?: string;
|
|
14
|
+
/** If present, a hard-hitl grant is single-use: consumed on the ledger, a replay is denied. */
|
|
15
|
+
nonce?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface GateResult {
|
|
18
|
+
blocked: boolean;
|
|
19
|
+
level: RiskLevel;
|
|
20
|
+
requiresApproval: boolean;
|
|
21
|
+
reasons: string[];
|
|
22
|
+
}
|
|
23
|
+
/** All of actor/token/rationale must be present and non-empty (whitespace-only counts as empty). */
|
|
24
|
+
export declare function isValidApproval(a?: Approval): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* @implements A-SPEC-133
|
|
27
|
+
* Does this well-formed approval AUTHORIZE this specific action, right now? This is the seam P2
|
|
28
|
+
* adds beside isValidApproval: the gates ask "covers?" instead of "well-formed?", so one token
|
|
29
|
+
* stops being a master key. Pure — `now` is passed in (the action timestamp), never read from the
|
|
30
|
+
* clock, so the predicate stays deterministic and testable.
|
|
31
|
+
*
|
|
32
|
+
* - malformed → false (isValidApproval's meaning is preserved).
|
|
33
|
+
* - expired (epoch comparison, both operands must parse; unreadable expires OR now → false). Absent expires → no expiry.
|
|
34
|
+
* - scope present → true iff some entry FULL-matches (kind exact or `*`; the pattern is anchored
|
|
35
|
+
* at both ends, `*` a wildcard run, trailing `/` a directory prefix). No entry matches → false: an existing-but-empty scope denies
|
|
36
|
+
* everything, which is the safe reading of "scoped but matches nothing".
|
|
37
|
+
* - scope absent → true: the documented UNSCOPED master-key path (the operator's session token).
|
|
38
|
+
*/
|
|
39
|
+
export declare function approvalCovers(a: Approval | undefined, action: {
|
|
40
|
+
kind: string;
|
|
41
|
+
target: string;
|
|
42
|
+
}, now: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* @implements A-SPEC-133
|
|
45
|
+
* Has this single-use nonce already been spent? Sync-reads the append-only provenance chain for a
|
|
46
|
+
* `nonce-consumed` entry carrying the nonce. A missing/unreadable ledger means nothing was consumed
|
|
47
|
+
* yet — false, never a throw (the gate must not crash on a fresh project).
|
|
48
|
+
*/
|
|
49
|
+
export declare function isNonceConsumed(nonce: string, ledgerFile: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Graduated response for a bare risk level, with no notion of approval.
|
|
52
|
+
* Pure: no I/O, no clock, no randomness.
|
|
53
|
+
*
|
|
54
|
+
* `enforcement: 'off'` softens `confirm` to a clean pass, but MUST NOT soften
|
|
55
|
+
* `hard-hitl` -- that is the non-bypassable minimum this gate exists to guarantee.
|
|
56
|
+
*/
|
|
57
|
+
export declare function gradedResponse(level: RiskLevel, enforcement: Enforcement): GateResult;
|
|
58
|
+
/**
|
|
59
|
+
* @implements A-SPEC-125.2
|
|
60
|
+
* Pure decision function: given a risk assessment, an optional out-of-band
|
|
61
|
+
* approval, and an enforcement mode, decides whether the action is blocked.
|
|
62
|
+
*
|
|
63
|
+
* No I/O and no ledger writes happen here -- recording a DecisionEvent is a
|
|
64
|
+
* separate caller step (see decision-ledger.ts), so a ledger write failure
|
|
65
|
+
* can never change the gate verdict.
|
|
66
|
+
*
|
|
67
|
+
* hard-hitl is non-bypassable: only a valid Approval (actor+token+rationale,
|
|
68
|
+
* all non-empty) unblocks it. enforcement:'off' softens confirm to a pass,
|
|
69
|
+
* but never softens hard-hitl.
|
|
70
|
+
*/
|
|
71
|
+
export declare function riskGate(assessment: RiskAssessment, approval?: Approval, enforcement?: Enforcement, cover?: {
|
|
72
|
+
action: {
|
|
73
|
+
kind: string;
|
|
74
|
+
target: string;
|
|
75
|
+
};
|
|
76
|
+
now: string;
|
|
77
|
+
}): GateResult;
|