@coderifts/agent-guard 1.6.1 → 1.6.2
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/README.md +87 -0
- package/dist/cjs/index.d.ts +6 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +9 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/with-coderifts.d.ts +131 -0
- package/dist/cjs/with-coderifts.d.ts.map +1 -0
- package/dist/cjs/with-coderifts.js +202 -0
- package/dist/cjs/with-coderifts.js.map +1 -0
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/with-coderifts.d.ts +131 -0
- package/dist/esm/with-coderifts.d.ts.map +1 -0
- package/dist/esm/with-coderifts.js +199 -0
- package/dist/esm/with-coderifts.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,93 @@ const { tools, coverage } = guardToolRegistry(rawTools, { guard: { client } });
|
|
|
55
55
|
// register ONLY `tools` with your agent SDK; coverage === 'COMPLETE' ⇒ no mutator is reachable raw.
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
### One-call orchestration with `withCodeRifts` (S1 + S2)
|
|
59
|
+
|
|
60
|
+
`withCodeRifts` wraps `guardToolRegistry` behind a single call that takes a **mandatory** `operation`
|
|
61
|
+
and returns the protected tools plus **two separate coverage statements**: the registry's own report and
|
|
62
|
+
a narrower, product-level composition assurance. Register **only** the returned `tools`:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { withCodeRifts } from '@coderifts/agent-guard';
|
|
66
|
+
|
|
67
|
+
const { tools, registry_report, composition_assurance } = withCodeRifts({
|
|
68
|
+
tools: rawTools, // your raw tool list
|
|
69
|
+
client, // the CodeRifts client (same one guardToolRegistry expects)
|
|
70
|
+
operation: 'merge', // REQUIRED — no default (see below)
|
|
71
|
+
});
|
|
72
|
+
// register ONLY `tools` with your agent SDK. Anything the host registers directly is OUTSIDE
|
|
73
|
+
// the guard — the composition can only protect the table it returns.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Why `operation` is mandatory (no default).** Receipts bind to an operation and `merge` is not
|
|
77
|
+
`deploy`, so a silent default would evaluate a deployment under merge semantics. `operation` is the
|
|
78
|
+
session-level default for **generic** mutating tools only; a tool with a specialised mutation class
|
|
79
|
+
(`mutating_deploy`, `mutating_publish`, `mutating_vcs`, …) still derives its own operation from that
|
|
80
|
+
class — `operation` does not override it.
|
|
81
|
+
|
|
82
|
+
**The two scopes — the point of this call.** The return carries two coverage statements answering two
|
|
83
|
+
different questions. Captured output from a real call against the current build (a clean list of two
|
|
84
|
+
mutating tools):
|
|
85
|
+
|
|
86
|
+
```jsonc
|
|
87
|
+
// composition_assurance — shown WHOLE:
|
|
88
|
+
{
|
|
89
|
+
"coverage": "PARTIAL",
|
|
90
|
+
"inescapable_runtime": false,
|
|
91
|
+
"residuals": ["composition_call_policy_incomplete"]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// registry_report — abbreviated (… marks fields elided here, NOT trimmed to read cleaner):
|
|
95
|
+
{
|
|
96
|
+
"coverage": "COMPLETE",
|
|
97
|
+
"guarded_mutators": ["edit_file", "write_config"],
|
|
98
|
+
"unguarded_mutators": [],
|
|
99
|
+
"claim": { "inescapable_runtime": true, "inescapable_merge": false, "inescapable_deploy": false },
|
|
100
|
+
"warnings": []
|
|
101
|
+
// … version, protected_tools, readonly_passthrough, unknown_treated_as, siblings
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- **`registry_report`** is the truth about the tool table that was wrapped. It may legitimately say
|
|
106
|
+
`COMPLETE` with `inescapable_runtime: true` — every mutator wrapped, none reachable raw.
|
|
107
|
+
`withCodeRifts` passes it through **untouched**.
|
|
108
|
+
- **`composition_assurance`** is the narrower, product-level statement — what `withCodeRifts` itself
|
|
109
|
+
claims. Today it reports `PARTIAL` with `inescapable_runtime: false` and the residual
|
|
110
|
+
`composition_call_policy_incomplete`, because call-time policy and receipt carry-forward are not yet
|
|
111
|
+
delivered.
|
|
112
|
+
- **This is deliberate, not a defect.** The composition will not claim runtime inescapability it cannot
|
|
113
|
+
yet deliver.
|
|
114
|
+
|
|
115
|
+
Reconciling with the `guardToolRegistry` note above (`coverage === 'COMPLETE' ⇒ no mutator is reachable
|
|
116
|
+
raw`): that claim is exactly what `registry_report` states, and it **remains true** — `withCodeRifts`
|
|
117
|
+
does not weaken it. `composition_assurance` answers a *different* question: not "is every mutator
|
|
118
|
+
wrapped" (true today) but "is the whole execution path through this composition inescapable yet" (not
|
|
119
|
+
yet). The composition says so rather than borrowing the registry's answer.
|
|
120
|
+
|
|
121
|
+
**What you get today:** one entry point, every mutator in the returned table wrapped fail-closed, and an
|
|
122
|
+
honest composition statement. **What you do not get yet:** any product-level claim of runtime
|
|
123
|
+
inescapability — `composition_assurance.inescapable_runtime` stays `false` until the later slices land.
|
|
124
|
+
|
|
125
|
+
**`requireCoverage?` (optional).** Aborts construction when the **registry** coverage is weaker than
|
|
126
|
+
required, by the ordering `COMPLETE > PARTIAL > BYPASSED > UNKNOWN`. It constrains the **registry surface
|
|
127
|
+
only** — it **cannot** demand product-level inescapability (unreachable until later slices), and a green
|
|
128
|
+
construction under it is not a product-level enforcement guarantee.
|
|
129
|
+
|
|
130
|
+
**`unknownToolPolicy` defaults to `'mutating'`.** An unclassified tool (no `mutationClass`, no
|
|
131
|
+
name-heuristic match) is treated as a mutator and wrapped — never silently downgraded to readonly, which
|
|
132
|
+
would hide a raw mutating capability behind a green result. Pass `'readonly'` / `'reject'` explicitly to
|
|
133
|
+
change it.
|
|
134
|
+
|
|
135
|
+
> **Known limitation — `forceReadonly` vs. an explicit `mutationClass`.** `forceReadonly` has **no
|
|
136
|
+
> effect** on a tool the caller declared with an explicit `mutationClass`: class resolution returns on
|
|
137
|
+
> `mutationClass` **before** `forceReadonly` is consulted, so the tool stays a wrapped mutator and **no
|
|
138
|
+
> warning or residual is produced**. A caller who both sets `mutationClass: 'mutating'` and lists that
|
|
139
|
+
> tool in `forceReadonly` gets **no signal that their `forceReadonly` was ignored**. `forceReadonly` only
|
|
140
|
+
> downgrades a tool whose mutating status came from the name heuristic.
|
|
141
|
+
|
|
142
|
+
**Not in this yet** (do not infer these from the one-call ergonomics): call-time policy, automatic
|
|
143
|
+
binders, receipt carry-forward, WARN monitoring, and framework adapters.
|
|
144
|
+
|
|
58
145
|
## Guarantees (tsc-verified)
|
|
59
146
|
|
|
60
147
|
- **Fail-closed by default** — any ambiguity, integrity failure, or unknown state resolves to `STOP`.
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Preflight contract changes before they execute. Security core FROZEN (agent-guard-api v1.0).
|
|
5
5
|
*
|
|
6
|
+
* An additive orchestration layer (withCodeRifts) now sits ALONGSIDE the frozen security core: it
|
|
7
|
+
* composes the frozen primitives behind one entry point and reports a separate, narrower
|
|
8
|
+
* composition-level assurance. It never modifies or re-decides any frozen primitive.
|
|
9
|
+
*
|
|
6
10
|
* @example
|
|
7
11
|
* ```ts
|
|
8
12
|
* import { guardToolCall } from '@coderifts/agent-guard';
|
|
@@ -38,4 +42,6 @@ export { deployGate } from './deploy-gate.js';
|
|
|
38
42
|
export type { DeployGateInput, DeployGateDecision, DeployReceiptView, DeployRequiredContext, DeployEnforcementState, DeployTarget, DeployGateReason, } from './deploy-gate.js';
|
|
39
43
|
export { coverageReport } from './coverage-report.js';
|
|
40
44
|
export type { CoverageReportInput, CoverageReport, Applicability, PlacementId, PlacementStrength, OverallCoverage, HonestClaimKey, PerPlacementRow, RuntimePlacementInput, MergePlacementInput, DeployPlacementInput, ContentPlacementInput, } from './coverage-report.js';
|
|
45
|
+
export { withCodeRifts } from './with-coderifts.js';
|
|
46
|
+
export type { WithCodeRiftsInput, WithCodeRiftsResult, WithCodeRiftsRegistryConfig, CompositionAssurance, } from './with-coderifts.js';
|
|
41
47
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Preflight contract changes before they execute. Security core FROZEN (agent-guard-api v1.0).
|
|
6
6
|
*
|
|
7
|
+
* An additive orchestration layer (withCodeRifts) now sits ALONGSIDE the frozen security core: it
|
|
8
|
+
* composes the frozen primitives behind one entry point and reports a separate, narrower
|
|
9
|
+
* composition-level assurance. It never modifies or re-decides any frozen primitive.
|
|
10
|
+
*
|
|
7
11
|
* @example
|
|
8
12
|
* ```ts
|
|
9
13
|
* import { guardToolCall } from '@coderifts/agent-guard';
|
|
@@ -19,7 +23,7 @@
|
|
|
19
23
|
* ```
|
|
20
24
|
*/
|
|
21
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.coverageReport = exports.deployGate = exports.gateDecision = exports.RegistryConstructionError = exports.guardToolRegistry = exports.globToRegExp = exports.matchGlob = exports.resolveArtifacts = exports.deriveKeySignal = exports.pathClass = exports.classifyCommand = exports.projectState = exports.emptySessionState = exports.computeTainted = exports.evaluate = exports.updateSession = exports.SESSION_TAINT_VERSION = exports.SessionTaintTracker = exports.readDecision = exports.computeBundleFingerprint = exports.computeArtifactDigest = exports.evaluateEnvelope = exports.canonicalJson = exports.computeBodyHash = exports.bindReceiptToEnvelope = exports.DETECTOR_VERSION = exports.builtinDetector = exports.guardToolCall = void 0;
|
|
26
|
+
exports.withCodeRifts = exports.coverageReport = exports.deployGate = exports.gateDecision = exports.RegistryConstructionError = exports.guardToolRegistry = exports.globToRegExp = exports.matchGlob = exports.resolveArtifacts = exports.deriveKeySignal = exports.pathClass = exports.classifyCommand = exports.projectState = exports.emptySessionState = exports.computeTainted = exports.evaluate = exports.updateSession = exports.SESSION_TAINT_VERSION = exports.SessionTaintTracker = exports.readDecision = exports.computeBundleFingerprint = exports.computeArtifactDigest = exports.evaluateEnvelope = exports.canonicalJson = exports.computeBodyHash = exports.bindReceiptToEnvelope = exports.DETECTOR_VERSION = exports.builtinDetector = exports.guardToolCall = void 0;
|
|
23
27
|
var guard_js_1 = require("./guard.js");
|
|
24
28
|
Object.defineProperty(exports, "guardToolCall", { enumerable: true, get: function () { return guard_js_1.guardToolCall; } });
|
|
25
29
|
var detector_js_1 = require("./detector.js");
|
|
@@ -70,4 +74,8 @@ Object.defineProperty(exports, "deployGate", { enumerable: true, get: function (
|
|
|
70
74
|
// enforcement-coverage report (#9) — the PURE tetrad aggregator. No I/O; reads the primitives' states.
|
|
71
75
|
var coverage_report_js_1 = require("./coverage-report.js");
|
|
72
76
|
Object.defineProperty(exports, "coverageReport", { enumerable: true, get: function () { return coverage_report_js_1.coverageReport; } });
|
|
77
|
+
// withCodeRifts (S1) — additive orchestration ABOVE the frozen primitives. Wraps guardToolRegistry
|
|
78
|
+
// with a mandatory operation; reports the registry's untouched report + a narrower composition assurance.
|
|
79
|
+
var with_coderifts_js_1 = require("./with-coderifts.js");
|
|
80
|
+
Object.defineProperty(exports, "withCodeRifts", { enumerable: true, get: function () { return with_coderifts_js_1.withCodeRifts; } });
|
|
73
81
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;;;AAEH,uCAA2C;AAAlC,yGAAA,aAAa,OAAA;AACtB,6CAAkE;AAAzD,8GAAA,eAAe,OAAA;AAAE,+GAAA,gBAAgB,OAAA;AAC1C,2FAA2F;AAC3F,2DAA6F;AAApF,2HAAA,qBAAqB,OAAA;AAAE,qHAAA,eAAe,OAAA;AAAE,mHAAA,aAAa,OAAA;AAE9D,sGAAsG;AACtG,+DAA+D;AAC/D,6DAA0G;AAAjG,uHAAA,gBAAgB,OAAA;AAAE,4HAAA,qBAAqB,OAAA;AAAE,+HAAA,wBAAwB,OAAA;AAE1E,sCAA8C;AAArC,mGAAA,YAAY,OAAA;AACrB,+FAA+F;AAC/F,uDAG4B;AAF1B,uHAAA,mBAAmB,OAAA;AAAE,yHAAA,qBAAqB,OAAA;AAAE,iHAAA,aAAa,OAAA;AAAE,4GAAA,QAAQ,OAAA;AAAE,kHAAA,cAAc,OAAA;AACnF,qHAAA,iBAAiB,OAAA;AAAE,gHAAA,YAAY,OAAA;AAAE,mHAAA,eAAe,OAAA;AAAE,6GAAA,SAAS,OAAA;AAAE,mHAAA,eAAe,OAAA;AA2B9E,kGAAkG;AAClG,wFAAwF;AACxF,+DAAqE;AAA5D,wHAAA,OAAO,OAAoB;AAKpC,uDAA6D;AAApD,6GAAA,SAAS,OAAA;AAAE,gHAAA,YAAY,OAAA;AAEhC,qGAAqG;AACrG,uDAAkF;AAAzE,qHAAA,iBAAiB,OAAA;AAAE,6HAAA,yBAAyB,OAAA;AAarD,uGAAuG;AACvG,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AAMrB,oGAAoG;AACpG,mDAA8C;AAArC,4GAAA,UAAU,OAAA;AAMnB,uGAAuG;AACvG,2DAAsD;AAA7C,oHAAA,cAAc,OAAA;AAOvB,mGAAmG;AACnG,0GAA0G;AAC1G,yDAAoD;AAA3C,kHAAA,aAAa,OAAA"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* withCodeRifts — additive orchestration layer above the frozen security core (slices S1 + S2).
|
|
3
|
+
*
|
|
4
|
+
* S1 DELIVERS: a single entry point that wraps the frozen `guardToolRegistry` with a MANDATORY
|
|
5
|
+
* `operation` and returns, side by side, (a) the registry's own untouched coverage report and (b) a
|
|
6
|
+
* separately-computed, deliberately-narrower composition-level assurance. It performs NO IO — every
|
|
7
|
+
* input is host-supplied, exactly like the primitives it composes.
|
|
8
|
+
*
|
|
9
|
+
* S2 ADDS startup honesty: it refuses to return a result that would LOOK protected while protection is
|
|
10
|
+
* incomplete. Concretely S2 adds — (1) an explicit `unknownToolPolicy` default of 'mutating' when the
|
|
11
|
+
* caller did not specify one, so an unclassified tool is never silently downgraded to readonly;
|
|
12
|
+
* (2) an optional `requireCoverage` that aborts construction when the registry's coverage is weaker
|
|
13
|
+
* than required, by an EXPLICIT strength ordering; (3) weakening-override residuals recorded onto
|
|
14
|
+
* `composition_assurance` so the trace survives into any later coverageReport consumer. S2 adds NO
|
|
15
|
+
* call-time behaviour.
|
|
16
|
+
*
|
|
17
|
+
* S2 does NOT re-guard what the registry already fails closed on. A `forceReadonly`/`classify`
|
|
18
|
+
* downgrade of a heuristic mutator is, under the registry's default `failOnUnguardedMutator:true`,
|
|
19
|
+
* already a thrown `FORCE_READONLY_MUTATOR` (no report exists); when the caller passes
|
|
20
|
+
* `failOnUnguardedMutator:false` it is already registry coverage 'BYPASSED', which fails any
|
|
21
|
+
* `requireCoverage` above BYPASSED by the ordering alone. So there is deliberately NO second
|
|
22
|
+
* composition-level abort for weakening overrides — that would create two places that must agree. The
|
|
23
|
+
* composition merely RECORDS the residual (composition_forced_readonly_on_heuristic_mutator /
|
|
24
|
+
* composition_unknown_treated_as_readonly) so the reason the composition is narrower than it looks is
|
|
25
|
+
* preserved on the assurance object. Registry-thrown errors propagate UNCHANGED (never wrapped or
|
|
26
|
+
* swallowed).
|
|
27
|
+
*
|
|
28
|
+
* How the weakening residuals are derived, and their KNOWN LIMITATION (a frozen-registry property, not
|
|
29
|
+
* a composition property): both residuals are read SOLELY from `registry_report.warnings` — the
|
|
30
|
+
* composition never recomputes classification. The registry emits `force_readonly_on_mutator_heuristic`
|
|
31
|
+
* ONLY for the downgrade of a HEURISTIC-classified mutator (a tool with no `classify` entry and no
|
|
32
|
+
* `mutationClass`, whose NAME heuristic is mutating), reached via `forceReadonly` OR a `classify`
|
|
33
|
+
* entry. Crucially, `forceReadonly` has NO effect on a tool the caller declared with an explicit
|
|
34
|
+
* `mutationClass`: `resolveClass` (tool-registry.ts:154-165) returns on `tool.mutationClass` at :157,
|
|
35
|
+
* BEFORE `forceReadonly` is consulted at :158 — so such a tool resolves `mutating`, stays guarded,
|
|
36
|
+
* yields coverage 'COMPLETE', and emits NO warning. The composition therefore produces NO residual in
|
|
37
|
+
* that case and CANNOT detect it: the caller's `forceReadonly` was silently ignored by the frozen
|
|
38
|
+
* registry. This is a limitation of the frozen registry surface, recorded (not worked around) here and
|
|
39
|
+
* pinned by a "documented limitation" test. The residual is named
|
|
40
|
+
* `composition_forced_readonly_on_heuristic_mutator` so it does not promise coverage of the case it
|
|
41
|
+
* cannot see.
|
|
42
|
+
*
|
|
43
|
+
* STILL DELIBERATELY OUT OF SCOPE (later slices; not stubbed, not implied here): call-time policy,
|
|
44
|
+
* automatic binders, receipt carry-forward, WARN monitoring, framework adapters, artifact resolution.
|
|
45
|
+
*
|
|
46
|
+
* THE TWO-SCOPE RULE (never merged):
|
|
47
|
+
* - `registry_report` is EXACTLY what `guardToolRegistry` returned, passed through untouched. It may
|
|
48
|
+
* legitimately state coverage 'COMPLETE' and claim.inescapable_runtime true — that is the runtime
|
|
49
|
+
* tool-boundary's own honest truth (Placement A).
|
|
50
|
+
* - `composition_assurance` is the narrower PRODUCT-level statement — what withCodeRifts as a whole
|
|
51
|
+
* is willing to claim today. It is computed SEPARATELY and never rewrites the registry's verdict.
|
|
52
|
+
* Its S1 semantics are UNCHANGED in S2: coverage stays 'PARTIAL', inescapable_runtime stays false
|
|
53
|
+
* via the same COMPOSITION_CALL_POLICY_COMPLETE conjunction. S2 only appends residuals.
|
|
54
|
+
* The two truths coexist; neither is derived by mutating the other.
|
|
55
|
+
*
|
|
56
|
+
* `requireCoverage` scope (read this before trusting a green construction): it constrains the
|
|
57
|
+
* REGISTRY-level coverage ONLY. It CANNOT be used to demand composition-level runtime inescapability,
|
|
58
|
+
* because S1's invariant makes that unreachable until S3 (binders) and S5 (receipt carry-forward)
|
|
59
|
+
* land. A caller passing `requireCoverage:'COMPLETE'` is asserting an expectation about the registry
|
|
60
|
+
* tool-boundary surface — NOT a product-level guarantee. The abort text and this doc say so explicitly
|
|
61
|
+
* so nobody reads a green construction as product-level enforcement.
|
|
62
|
+
*
|
|
63
|
+
* Operation semantics (accurate scope): `operationForClass` (tool-registry.ts) already derives a
|
|
64
|
+
* per-tool operation from the tool's mutation class, OVERRIDING the guard config for specialised
|
|
65
|
+
* classes (deploy→'deploy', publish→'publish', vcs-merge→'merge', …). The mandatory `operation` here
|
|
66
|
+
* therefore governs generic mutating tools and acts as the session-level default; it does NOT override
|
|
67
|
+
* a deploy-class tool's 'deploy'. It is mandatory because receipts bind to an operation and
|
|
68
|
+
* merge != deploy: a silent default would risk evaluating a deploy under merge semantics.
|
|
69
|
+
*
|
|
70
|
+
* Abort discipline: pre-registry input problems (missing/empty operation, missing client, an invalid
|
|
71
|
+
* requireCoverage value) are collected and thrown as ONE error listing ALL of them. The
|
|
72
|
+
* requireCoverage-vs-actual check is necessarily sequenced AFTER the registry call (it needs the
|
|
73
|
+
* registry's coverage), so it is its own abort; it cannot be batched with the pre-registry problems
|
|
74
|
+
* because a valid operation+client are required even to reach the registry.
|
|
75
|
+
*/
|
|
76
|
+
import type { RawTool, ProtectedTool, EnforcementCoverage, RegistryCoverageReport, ToolBinder, ToolMutationClass } from './tool-registry.js';
|
|
77
|
+
import type { GuardConfig } from './types.js';
|
|
78
|
+
/** Registry config fields callers may forward untouched (S1/S2 do not re-declare them). */
|
|
79
|
+
export type WithCodeRiftsRegistryConfig = {
|
|
80
|
+
unknownToolPolicy?: 'mutating' | 'readonly' | 'reject';
|
|
81
|
+
classify?: Record<string, ToolMutationClass>;
|
|
82
|
+
binders?: Record<string, ToolBinder>;
|
|
83
|
+
forceReadonly?: string[];
|
|
84
|
+
failOnUnguardedMutator?: boolean;
|
|
85
|
+
};
|
|
86
|
+
export type WithCodeRiftsInput = {
|
|
87
|
+
/** Raw tool list handed to the frozen guardToolRegistry (required). */
|
|
88
|
+
tools: RawTool[];
|
|
89
|
+
/** The CodeRifts client guardToolRegistry's config.guard.client expects (required; checked at construction). */
|
|
90
|
+
client: GuardConfig['client'];
|
|
91
|
+
/**
|
|
92
|
+
* REQUIRED session-level operation — no default. Receipts bind to an operation and merge != deploy,
|
|
93
|
+
* so a silent default would risk evaluating a deploy under merge semantics. Governs generic mutating
|
|
94
|
+
* tools; specialised classes keep their operationForClass-derived operation.
|
|
95
|
+
*/
|
|
96
|
+
operation: string;
|
|
97
|
+
/** Optional; carried through untouched. Artifact resolution is a later slice — S1 invents no use for it. */
|
|
98
|
+
repository?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Optional (S2). Minimum REGISTRY-level coverage required at construction. If the registry's actual
|
|
101
|
+
* coverage is weaker than this (by the COVERAGE_STRENGTH ordering), construction ABORTS. It does NOT
|
|
102
|
+
* demand composition-level inescapability (unreachable until S3/S5) — a green construction here is
|
|
103
|
+
* NOT a product-level runtime-enforcement guarantee.
|
|
104
|
+
*/
|
|
105
|
+
requireCoverage?: EnforcementCoverage;
|
|
106
|
+
/** Optional passthrough of existing GuardToolRegistryConfig fields (forwarded as given). */
|
|
107
|
+
registry?: WithCodeRiftsRegistryConfig;
|
|
108
|
+
};
|
|
109
|
+
/** The narrower product-level statement, computed separately from the registry's own report. */
|
|
110
|
+
export type CompositionAssurance = {
|
|
111
|
+
coverage: EnforcementCoverage;
|
|
112
|
+
inescapable_runtime: boolean;
|
|
113
|
+
residuals: string[];
|
|
114
|
+
};
|
|
115
|
+
export type WithCodeRiftsResult = {
|
|
116
|
+
tools: ProtectedTool[];
|
|
117
|
+
/** Untouched RegistryCoverageReport — the registry's own truth (may be COMPLETE / inescapable). */
|
|
118
|
+
registry_report: RegistryCoverageReport;
|
|
119
|
+
/** Product-level assurance — deliberately narrower than the registry. */
|
|
120
|
+
composition_assurance: CompositionAssurance;
|
|
121
|
+
/** Carried through untouched from input when present (no resolution behaviour in S1/S2). */
|
|
122
|
+
repository?: string;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Wrap guardToolRegistry with a mandatory operation and a separately-computed composition assurance.
|
|
126
|
+
* Fails at CONSTRUCTION (never at first tool call) for a missing client, a missing/empty operation, an
|
|
127
|
+
* invalid requireCoverage value, or (S2) an unmet requireCoverage. Registry-thrown construction errors
|
|
128
|
+
* propagate UNCHANGED.
|
|
129
|
+
*/
|
|
130
|
+
export declare function withCodeRifts(input: WithCodeRiftsInput): WithCodeRiftsResult;
|
|
131
|
+
//# sourceMappingURL=with-coderifts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-coderifts.d.ts","sourceRoot":"","sources":["../../src/with-coderifts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AAGH,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EAEtB,UAAU,EACV,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,2FAA2F;AAC3F,MAAM,MAAM,2BAA2B,GAAG;IACxC,iBAAiB,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,uEAAuE;IACvE,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,gHAAgH;IAChH,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC9B;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,4GAA4G;IAC5G,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,2BAA2B,CAAC;CACxC,CAAC;AAEF,gGAAgG;AAChG,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,mGAAmG;IACnG,eAAe,EAAE,sBAAsB,CAAC;IACxC,yEAAyE;IACzE,qBAAqB,EAAE,oBAAoB,CAAC;IAC5C,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAuCF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,CA8F5E"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* withCodeRifts — additive orchestration layer above the frozen security core (slices S1 + S2).
|
|
4
|
+
*
|
|
5
|
+
* S1 DELIVERS: a single entry point that wraps the frozen `guardToolRegistry` with a MANDATORY
|
|
6
|
+
* `operation` and returns, side by side, (a) the registry's own untouched coverage report and (b) a
|
|
7
|
+
* separately-computed, deliberately-narrower composition-level assurance. It performs NO IO — every
|
|
8
|
+
* input is host-supplied, exactly like the primitives it composes.
|
|
9
|
+
*
|
|
10
|
+
* S2 ADDS startup honesty: it refuses to return a result that would LOOK protected while protection is
|
|
11
|
+
* incomplete. Concretely S2 adds — (1) an explicit `unknownToolPolicy` default of 'mutating' when the
|
|
12
|
+
* caller did not specify one, so an unclassified tool is never silently downgraded to readonly;
|
|
13
|
+
* (2) an optional `requireCoverage` that aborts construction when the registry's coverage is weaker
|
|
14
|
+
* than required, by an EXPLICIT strength ordering; (3) weakening-override residuals recorded onto
|
|
15
|
+
* `composition_assurance` so the trace survives into any later coverageReport consumer. S2 adds NO
|
|
16
|
+
* call-time behaviour.
|
|
17
|
+
*
|
|
18
|
+
* S2 does NOT re-guard what the registry already fails closed on. A `forceReadonly`/`classify`
|
|
19
|
+
* downgrade of a heuristic mutator is, under the registry's default `failOnUnguardedMutator:true`,
|
|
20
|
+
* already a thrown `FORCE_READONLY_MUTATOR` (no report exists); when the caller passes
|
|
21
|
+
* `failOnUnguardedMutator:false` it is already registry coverage 'BYPASSED', which fails any
|
|
22
|
+
* `requireCoverage` above BYPASSED by the ordering alone. So there is deliberately NO second
|
|
23
|
+
* composition-level abort for weakening overrides — that would create two places that must agree. The
|
|
24
|
+
* composition merely RECORDS the residual (composition_forced_readonly_on_heuristic_mutator /
|
|
25
|
+
* composition_unknown_treated_as_readonly) so the reason the composition is narrower than it looks is
|
|
26
|
+
* preserved on the assurance object. Registry-thrown errors propagate UNCHANGED (never wrapped or
|
|
27
|
+
* swallowed).
|
|
28
|
+
*
|
|
29
|
+
* How the weakening residuals are derived, and their KNOWN LIMITATION (a frozen-registry property, not
|
|
30
|
+
* a composition property): both residuals are read SOLELY from `registry_report.warnings` — the
|
|
31
|
+
* composition never recomputes classification. The registry emits `force_readonly_on_mutator_heuristic`
|
|
32
|
+
* ONLY for the downgrade of a HEURISTIC-classified mutator (a tool with no `classify` entry and no
|
|
33
|
+
* `mutationClass`, whose NAME heuristic is mutating), reached via `forceReadonly` OR a `classify`
|
|
34
|
+
* entry. Crucially, `forceReadonly` has NO effect on a tool the caller declared with an explicit
|
|
35
|
+
* `mutationClass`: `resolveClass` (tool-registry.ts:154-165) returns on `tool.mutationClass` at :157,
|
|
36
|
+
* BEFORE `forceReadonly` is consulted at :158 — so such a tool resolves `mutating`, stays guarded,
|
|
37
|
+
* yields coverage 'COMPLETE', and emits NO warning. The composition therefore produces NO residual in
|
|
38
|
+
* that case and CANNOT detect it: the caller's `forceReadonly` was silently ignored by the frozen
|
|
39
|
+
* registry. This is a limitation of the frozen registry surface, recorded (not worked around) here and
|
|
40
|
+
* pinned by a "documented limitation" test. The residual is named
|
|
41
|
+
* `composition_forced_readonly_on_heuristic_mutator` so it does not promise coverage of the case it
|
|
42
|
+
* cannot see.
|
|
43
|
+
*
|
|
44
|
+
* STILL DELIBERATELY OUT OF SCOPE (later slices; not stubbed, not implied here): call-time policy,
|
|
45
|
+
* automatic binders, receipt carry-forward, WARN monitoring, framework adapters, artifact resolution.
|
|
46
|
+
*
|
|
47
|
+
* THE TWO-SCOPE RULE (never merged):
|
|
48
|
+
* - `registry_report` is EXACTLY what `guardToolRegistry` returned, passed through untouched. It may
|
|
49
|
+
* legitimately state coverage 'COMPLETE' and claim.inescapable_runtime true — that is the runtime
|
|
50
|
+
* tool-boundary's own honest truth (Placement A).
|
|
51
|
+
* - `composition_assurance` is the narrower PRODUCT-level statement — what withCodeRifts as a whole
|
|
52
|
+
* is willing to claim today. It is computed SEPARATELY and never rewrites the registry's verdict.
|
|
53
|
+
* Its S1 semantics are UNCHANGED in S2: coverage stays 'PARTIAL', inescapable_runtime stays false
|
|
54
|
+
* via the same COMPOSITION_CALL_POLICY_COMPLETE conjunction. S2 only appends residuals.
|
|
55
|
+
* The two truths coexist; neither is derived by mutating the other.
|
|
56
|
+
*
|
|
57
|
+
* `requireCoverage` scope (read this before trusting a green construction): it constrains the
|
|
58
|
+
* REGISTRY-level coverage ONLY. It CANNOT be used to demand composition-level runtime inescapability,
|
|
59
|
+
* because S1's invariant makes that unreachable until S3 (binders) and S5 (receipt carry-forward)
|
|
60
|
+
* land. A caller passing `requireCoverage:'COMPLETE'` is asserting an expectation about the registry
|
|
61
|
+
* tool-boundary surface — NOT a product-level guarantee. The abort text and this doc say so explicitly
|
|
62
|
+
* so nobody reads a green construction as product-level enforcement.
|
|
63
|
+
*
|
|
64
|
+
* Operation semantics (accurate scope): `operationForClass` (tool-registry.ts) already derives a
|
|
65
|
+
* per-tool operation from the tool's mutation class, OVERRIDING the guard config for specialised
|
|
66
|
+
* classes (deploy→'deploy', publish→'publish', vcs-merge→'merge', …). The mandatory `operation` here
|
|
67
|
+
* therefore governs generic mutating tools and acts as the session-level default; it does NOT override
|
|
68
|
+
* a deploy-class tool's 'deploy'. It is mandatory because receipts bind to an operation and
|
|
69
|
+
* merge != deploy: a silent default would risk evaluating a deploy under merge semantics.
|
|
70
|
+
*
|
|
71
|
+
* Abort discipline: pre-registry input problems (missing/empty operation, missing client, an invalid
|
|
72
|
+
* requireCoverage value) are collected and thrown as ONE error listing ALL of them. The
|
|
73
|
+
* requireCoverage-vs-actual check is necessarily sequenced AFTER the registry call (it needs the
|
|
74
|
+
* registry's coverage), so it is its own abort; it cannot be batched with the pre-registry problems
|
|
75
|
+
* because a valid operation+client are required even to reach the registry.
|
|
76
|
+
*/
|
|
77
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
78
|
+
exports.withCodeRifts = withCodeRifts;
|
|
79
|
+
const tool_registry_js_1 = require("./tool-registry.js");
|
|
80
|
+
/**
|
|
81
|
+
* Whether the composition's call-time policy is complete. FALSE in S1/S2: automatic binders (S3) and
|
|
82
|
+
* receipt carry-forward (S5) are not yet delivered, so the composition cannot claim runtime
|
|
83
|
+
* inescapability. Later slices AND additional conjuncts into the composition invariant below rather
|
|
84
|
+
* than replacing this one. UNCHANGED by S2.
|
|
85
|
+
*/
|
|
86
|
+
const COMPOSITION_CALL_POLICY_COMPLETE = false; // becomes true only once S3 (binders) + S5 (receipt carry-forward) land
|
|
87
|
+
const RESIDUAL_CALL_POLICY_INCOMPLETE = 'composition_call_policy_incomplete';
|
|
88
|
+
// S2 weakening-override residuals — derived SOLELY from registry report.warnings (never recomputed).
|
|
89
|
+
// The name says "heuristic_mutator" on purpose: the registry only emits the underlying warning for a
|
|
90
|
+
// downgrade of a HEURISTIC-classified mutator (see the header limitation), so the residual must not
|
|
91
|
+
// promise coverage of a downgrade it cannot see (an explicit-mutationClass tool).
|
|
92
|
+
const RESIDUAL_FORCED_READONLY = 'composition_forced_readonly_on_heuristic_mutator';
|
|
93
|
+
const RESIDUAL_UNKNOWN_READONLY = 'composition_unknown_treated_as_readonly';
|
|
94
|
+
/**
|
|
95
|
+
* Coverage strength ordering, strongest → weakest. `requireCoverage` aborts when the registry's ACTUAL
|
|
96
|
+
* coverage ranks strictly BELOW the required one. COMPLETE is the strongest; BYPASSED and UNKNOWN are
|
|
97
|
+
* the weakest and are unacceptable for any requirement above them. UNKNOWN is the absolute floor
|
|
98
|
+
* (fail-closed: an unobservable coverage never satisfies a requirement above it). Written explicitly
|
|
99
|
+
* so the ordering is not implicit in comparison operators elsewhere.
|
|
100
|
+
*/
|
|
101
|
+
const COVERAGE_STRENGTH = {
|
|
102
|
+
COMPLETE: 3,
|
|
103
|
+
PARTIAL: 2,
|
|
104
|
+
BYPASSED: 1,
|
|
105
|
+
UNKNOWN: 0,
|
|
106
|
+
};
|
|
107
|
+
/** Rank of a coverage string, or undefined for an unrecognised value (fail-closed: caller treats as floor). */
|
|
108
|
+
function coverageRank(coverage) {
|
|
109
|
+
return Object.prototype.hasOwnProperty.call(COVERAGE_STRENGTH, coverage)
|
|
110
|
+
? COVERAGE_STRENGTH[coverage]
|
|
111
|
+
: undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Wrap guardToolRegistry with a mandatory operation and a separately-computed composition assurance.
|
|
115
|
+
* Fails at CONSTRUCTION (never at first tool call) for a missing client, a missing/empty operation, an
|
|
116
|
+
* invalid requireCoverage value, or (S2) an unmet requireCoverage. Registry-thrown construction errors
|
|
117
|
+
* propagate UNCHANGED.
|
|
118
|
+
*/
|
|
119
|
+
function withCodeRifts(input) {
|
|
120
|
+
if (!input || typeof input !== 'object') {
|
|
121
|
+
throw new Error('withCodeRifts: input object is required');
|
|
122
|
+
}
|
|
123
|
+
// Pre-registry input validation — collect ALL problems, throw once (a valid operation + client are
|
|
124
|
+
// required even to reach the registry, so these cannot be batched with the post-registry check).
|
|
125
|
+
const problems = [];
|
|
126
|
+
if (typeof input.operation !== 'string' || input.operation.trim() === '') {
|
|
127
|
+
problems.push('`operation` is required and must be a non-empty string (receipts bind to an operation; merge != deploy, so there is no safe default)');
|
|
128
|
+
}
|
|
129
|
+
if (input.client == null) {
|
|
130
|
+
problems.push('`client` is required at construction (guardToolRegistry needs config.guard.client to wrap any mutating tool)');
|
|
131
|
+
}
|
|
132
|
+
if (input.requireCoverage !== undefined && coverageRank(input.requireCoverage) === undefined) {
|
|
133
|
+
problems.push(`\`requireCoverage\` must be one of COMPLETE | PARTIAL | BYPASSED | UNKNOWN (got ${JSON.stringify(input.requireCoverage)})`);
|
|
134
|
+
}
|
|
135
|
+
if (problems.length > 0) {
|
|
136
|
+
throw new Error(`withCodeRifts: construction aborted — ${problems.length} condition(s):\n`
|
|
137
|
+
+ problems.map((p) => ` - ${p}`).join('\n'));
|
|
138
|
+
}
|
|
139
|
+
const reg = input.registry ?? {};
|
|
140
|
+
// Defaults, applied ONLY when the caller did not specify them:
|
|
141
|
+
// - unknownToolPolicy → 'mutating': an unclassified tool must never SILENTLY become readonly (that
|
|
142
|
+
// would hide a raw mutating capability behind a green result). The caller may still explicitly pass
|
|
143
|
+
// 'readonly'/'reject'; an explicit 'readonly' that downgrades an unknown tool is surfaced as the
|
|
144
|
+
// composition_unknown_treated_as_readonly residual below.
|
|
145
|
+
// - failOnUnguardedMutator: NOT defaulted here — the registry owns its own default (true). Forward
|
|
146
|
+
// the caller's value if given so there is a single source of truth for it.
|
|
147
|
+
const config = {
|
|
148
|
+
guard: { client: input.client, operation: input.operation },
|
|
149
|
+
unknownToolPolicy: reg.unknownToolPolicy ?? 'mutating',
|
|
150
|
+
classify: reg.classify,
|
|
151
|
+
binders: reg.binders,
|
|
152
|
+
forceReadonly: reg.forceReadonly,
|
|
153
|
+
failOnUnguardedMutator: reg.failOnUnguardedMutator,
|
|
154
|
+
};
|
|
155
|
+
// Registry-thrown construction errors (INVALID_TOOL, DUPLICATE_TOOL_NAME, UNKNOWN_TOOL,
|
|
156
|
+
// FORCE_READONLY_MUTATOR, GUARD_CONFIG_INVALID) propagate UNCHANGED — never caught, wrapped, or
|
|
157
|
+
// swallowed. That is the real contract: the composition does not re-guard what the registry
|
|
158
|
+
// already fails closed on.
|
|
159
|
+
const { tools, report } = (0, tool_registry_js_1.guardToolRegistry)(input.tools, config);
|
|
160
|
+
// S2 requireCoverage — abort if the REGISTRY-level coverage is weaker than required. This is not a
|
|
161
|
+
// weakening-specific rule: BYPASSED (from a forced downgrade under failOnUnguardedMutator:false) is
|
|
162
|
+
// simply weaker than COMPLETE by the ordering, so it fails here for the same reason PARTIAL does.
|
|
163
|
+
if (input.requireCoverage !== undefined) {
|
|
164
|
+
const requiredRank = coverageRank(input.requireCoverage); // validated non-undefined pre-registry
|
|
165
|
+
const actualRank = coverageRank(report.coverage) ?? -1; // fail-closed: unknown coverage = below any floor
|
|
166
|
+
if (requiredRank !== undefined && actualRank < requiredRank) {
|
|
167
|
+
throw new Error(`withCodeRifts: requireCoverage not met — registry coverage '${report.coverage}' is weaker than required '${input.requireCoverage}' `
|
|
168
|
+
+ `(strength ordering COMPLETE > PARTIAL > BYPASSED > UNKNOWN). requireCoverage constrains the REGISTRY tool-boundary surface ONLY; `
|
|
169
|
+
+ `a green construction here is NOT a product-level runtime-inescapability guarantee — composition_assurance.inescapable_runtime stays false until S3/S5.`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Composition invariant — a conjunction later slices EXTEND (add conjuncts), never replace. Because
|
|
173
|
+
// COMPOSITION_CALL_POLICY_COMPLETE is false, the result is deterministically false regardless of the
|
|
174
|
+
// registry's own (possibly true) inescapable_runtime. UNCHANGED by S2.
|
|
175
|
+
const compositionInescapableRuntime = report.claim.inescapable_runtime && COMPOSITION_CALL_POLICY_COMPLETE;
|
|
176
|
+
// S2 residuals — DERIVED from the registry's own report.warnings, never recomputed. The registry
|
|
177
|
+
// conflates forceReadonly and classify downgrades into one warning string, so this maps to two
|
|
178
|
+
// causes (forced-readonly, unknown→readonly), not three (see recon item c).
|
|
179
|
+
const residuals = [RESIDUAL_CALL_POLICY_INCOMPLETE];
|
|
180
|
+
if (report.warnings.some((w) => w.startsWith('force_readonly_on_mutator_heuristic:'))) {
|
|
181
|
+
residuals.push(RESIDUAL_FORCED_READONLY);
|
|
182
|
+
}
|
|
183
|
+
if (report.warnings.includes('unknown_treated_as_readonly')) {
|
|
184
|
+
residuals.push(RESIDUAL_UNKNOWN_READONLY);
|
|
185
|
+
}
|
|
186
|
+
// 'PARTIAL' from the existing EnforcementCoverage union — never 'COMPLETE' while inescapable_runtime
|
|
187
|
+
// is false (that combination would contradict the registry's own formula). UNCHANGED by S2.
|
|
188
|
+
const composition_assurance = {
|
|
189
|
+
coverage: 'PARTIAL',
|
|
190
|
+
inescapable_runtime: compositionInescapableRuntime,
|
|
191
|
+
residuals,
|
|
192
|
+
};
|
|
193
|
+
const result = {
|
|
194
|
+
tools,
|
|
195
|
+
registry_report: report,
|
|
196
|
+
composition_assurance,
|
|
197
|
+
};
|
|
198
|
+
if (input.repository !== undefined)
|
|
199
|
+
result.repository = input.repository;
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=with-coderifts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-coderifts.js","sourceRoot":"","sources":["../../src/with-coderifts.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Preflight contract changes before they execute. Security core FROZEN (agent-guard-api v1.0).
|
|
5
5
|
*
|
|
6
|
+
* An additive orchestration layer (withCodeRifts) now sits ALONGSIDE the frozen security core: it
|
|
7
|
+
* composes the frozen primitives behind one entry point and reports a separate, narrower
|
|
8
|
+
* composition-level assurance. It never modifies or re-decides any frozen primitive.
|
|
9
|
+
*
|
|
6
10
|
* @example
|
|
7
11
|
* ```ts
|
|
8
12
|
* import { guardToolCall } from '@coderifts/agent-guard';
|
|
@@ -38,4 +42,6 @@ export { deployGate } from './deploy-gate.js';
|
|
|
38
42
|
export type { DeployGateInput, DeployGateDecision, DeployReceiptView, DeployRequiredContext, DeployEnforcementState, DeployTarget, DeployGateReason, } from './deploy-gate.js';
|
|
39
43
|
export { coverageReport } from './coverage-report.js';
|
|
40
44
|
export type { CoverageReportInput, CoverageReport, Applicability, PlacementId, PlacementStrength, OverallCoverage, HonestClaimKey, PerPlacementRow, RuntimePlacementInput, MergePlacementInput, DeployPlacementInput, ContentPlacementInput, } from './coverage-report.js';
|
|
45
|
+
export { withCodeRifts } from './with-coderifts.js';
|
|
46
|
+
export type { WithCodeRiftsInput, WithCodeRiftsResult, WithCodeRiftsRegistryConfig, CompositionAssurance, } from './with-coderifts.js';
|
|
41
47
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/index.js
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Preflight contract changes before they execute. Security core FROZEN (agent-guard-api v1.0).
|
|
5
5
|
*
|
|
6
|
+
* An additive orchestration layer (withCodeRifts) now sits ALONGSIDE the frozen security core: it
|
|
7
|
+
* composes the frozen primitives behind one entry point and reports a separate, narrower
|
|
8
|
+
* composition-level assurance. It never modifies or re-decides any frozen primitive.
|
|
9
|
+
*
|
|
6
10
|
* @example
|
|
7
11
|
* ```ts
|
|
8
12
|
* import { guardToolCall } from '@coderifts/agent-guard';
|
|
@@ -39,4 +43,7 @@ export { gateDecision } from './merge-gate.js';
|
|
|
39
43
|
export { deployGate } from './deploy-gate.js';
|
|
40
44
|
// enforcement-coverage report (#9) — the PURE tetrad aggregator. No I/O; reads the primitives' states.
|
|
41
45
|
export { coverageReport } from './coverage-report.js';
|
|
46
|
+
// withCodeRifts (S1) — additive orchestration ABOVE the frozen primitives. Wraps guardToolRegistry
|
|
47
|
+
// with a mandatory operation; reports the registry's untouched report + a narrower composition assurance.
|
|
48
|
+
export { withCodeRifts } from './with-coderifts.js';
|
|
42
49
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAClE,2FAA2F;AAC3F,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE7F,sGAAsG;AACtG,+DAA+D;AAC/D,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAE1G,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,+FAA+F;AAC/F,OAAO,EACL,mBAAmB,EAAE,qBAAqB,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EACnF,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,GAC7E,MAAM,oBAAoB,CAAC;AA0B5B,kGAAkG;AAClG,wFAAwF;AACxF,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAKrE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7D,qGAAqG;AACrG,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAalF,uGAAuG;AACvG,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAM/C,oGAAoG;AACpG,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAM9C,uGAAuG;AACvG,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAOtD,mGAAmG;AACnG,0GAA0G;AAC1G,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* withCodeRifts — additive orchestration layer above the frozen security core (slices S1 + S2).
|
|
3
|
+
*
|
|
4
|
+
* S1 DELIVERS: a single entry point that wraps the frozen `guardToolRegistry` with a MANDATORY
|
|
5
|
+
* `operation` and returns, side by side, (a) the registry's own untouched coverage report and (b) a
|
|
6
|
+
* separately-computed, deliberately-narrower composition-level assurance. It performs NO IO — every
|
|
7
|
+
* input is host-supplied, exactly like the primitives it composes.
|
|
8
|
+
*
|
|
9
|
+
* S2 ADDS startup honesty: it refuses to return a result that would LOOK protected while protection is
|
|
10
|
+
* incomplete. Concretely S2 adds — (1) an explicit `unknownToolPolicy` default of 'mutating' when the
|
|
11
|
+
* caller did not specify one, so an unclassified tool is never silently downgraded to readonly;
|
|
12
|
+
* (2) an optional `requireCoverage` that aborts construction when the registry's coverage is weaker
|
|
13
|
+
* than required, by an EXPLICIT strength ordering; (3) weakening-override residuals recorded onto
|
|
14
|
+
* `composition_assurance` so the trace survives into any later coverageReport consumer. S2 adds NO
|
|
15
|
+
* call-time behaviour.
|
|
16
|
+
*
|
|
17
|
+
* S2 does NOT re-guard what the registry already fails closed on. A `forceReadonly`/`classify`
|
|
18
|
+
* downgrade of a heuristic mutator is, under the registry's default `failOnUnguardedMutator:true`,
|
|
19
|
+
* already a thrown `FORCE_READONLY_MUTATOR` (no report exists); when the caller passes
|
|
20
|
+
* `failOnUnguardedMutator:false` it is already registry coverage 'BYPASSED', which fails any
|
|
21
|
+
* `requireCoverage` above BYPASSED by the ordering alone. So there is deliberately NO second
|
|
22
|
+
* composition-level abort for weakening overrides — that would create two places that must agree. The
|
|
23
|
+
* composition merely RECORDS the residual (composition_forced_readonly_on_heuristic_mutator /
|
|
24
|
+
* composition_unknown_treated_as_readonly) so the reason the composition is narrower than it looks is
|
|
25
|
+
* preserved on the assurance object. Registry-thrown errors propagate UNCHANGED (never wrapped or
|
|
26
|
+
* swallowed).
|
|
27
|
+
*
|
|
28
|
+
* How the weakening residuals are derived, and their KNOWN LIMITATION (a frozen-registry property, not
|
|
29
|
+
* a composition property): both residuals are read SOLELY from `registry_report.warnings` — the
|
|
30
|
+
* composition never recomputes classification. The registry emits `force_readonly_on_mutator_heuristic`
|
|
31
|
+
* ONLY for the downgrade of a HEURISTIC-classified mutator (a tool with no `classify` entry and no
|
|
32
|
+
* `mutationClass`, whose NAME heuristic is mutating), reached via `forceReadonly` OR a `classify`
|
|
33
|
+
* entry. Crucially, `forceReadonly` has NO effect on a tool the caller declared with an explicit
|
|
34
|
+
* `mutationClass`: `resolveClass` (tool-registry.ts:154-165) returns on `tool.mutationClass` at :157,
|
|
35
|
+
* BEFORE `forceReadonly` is consulted at :158 — so such a tool resolves `mutating`, stays guarded,
|
|
36
|
+
* yields coverage 'COMPLETE', and emits NO warning. The composition therefore produces NO residual in
|
|
37
|
+
* that case and CANNOT detect it: the caller's `forceReadonly` was silently ignored by the frozen
|
|
38
|
+
* registry. This is a limitation of the frozen registry surface, recorded (not worked around) here and
|
|
39
|
+
* pinned by a "documented limitation" test. The residual is named
|
|
40
|
+
* `composition_forced_readonly_on_heuristic_mutator` so it does not promise coverage of the case it
|
|
41
|
+
* cannot see.
|
|
42
|
+
*
|
|
43
|
+
* STILL DELIBERATELY OUT OF SCOPE (later slices; not stubbed, not implied here): call-time policy,
|
|
44
|
+
* automatic binders, receipt carry-forward, WARN monitoring, framework adapters, artifact resolution.
|
|
45
|
+
*
|
|
46
|
+
* THE TWO-SCOPE RULE (never merged):
|
|
47
|
+
* - `registry_report` is EXACTLY what `guardToolRegistry` returned, passed through untouched. It may
|
|
48
|
+
* legitimately state coverage 'COMPLETE' and claim.inescapable_runtime true — that is the runtime
|
|
49
|
+
* tool-boundary's own honest truth (Placement A).
|
|
50
|
+
* - `composition_assurance` is the narrower PRODUCT-level statement — what withCodeRifts as a whole
|
|
51
|
+
* is willing to claim today. It is computed SEPARATELY and never rewrites the registry's verdict.
|
|
52
|
+
* Its S1 semantics are UNCHANGED in S2: coverage stays 'PARTIAL', inescapable_runtime stays false
|
|
53
|
+
* via the same COMPOSITION_CALL_POLICY_COMPLETE conjunction. S2 only appends residuals.
|
|
54
|
+
* The two truths coexist; neither is derived by mutating the other.
|
|
55
|
+
*
|
|
56
|
+
* `requireCoverage` scope (read this before trusting a green construction): it constrains the
|
|
57
|
+
* REGISTRY-level coverage ONLY. It CANNOT be used to demand composition-level runtime inescapability,
|
|
58
|
+
* because S1's invariant makes that unreachable until S3 (binders) and S5 (receipt carry-forward)
|
|
59
|
+
* land. A caller passing `requireCoverage:'COMPLETE'` is asserting an expectation about the registry
|
|
60
|
+
* tool-boundary surface — NOT a product-level guarantee. The abort text and this doc say so explicitly
|
|
61
|
+
* so nobody reads a green construction as product-level enforcement.
|
|
62
|
+
*
|
|
63
|
+
* Operation semantics (accurate scope): `operationForClass` (tool-registry.ts) already derives a
|
|
64
|
+
* per-tool operation from the tool's mutation class, OVERRIDING the guard config for specialised
|
|
65
|
+
* classes (deploy→'deploy', publish→'publish', vcs-merge→'merge', …). The mandatory `operation` here
|
|
66
|
+
* therefore governs generic mutating tools and acts as the session-level default; it does NOT override
|
|
67
|
+
* a deploy-class tool's 'deploy'. It is mandatory because receipts bind to an operation and
|
|
68
|
+
* merge != deploy: a silent default would risk evaluating a deploy under merge semantics.
|
|
69
|
+
*
|
|
70
|
+
* Abort discipline: pre-registry input problems (missing/empty operation, missing client, an invalid
|
|
71
|
+
* requireCoverage value) are collected and thrown as ONE error listing ALL of them. The
|
|
72
|
+
* requireCoverage-vs-actual check is necessarily sequenced AFTER the registry call (it needs the
|
|
73
|
+
* registry's coverage), so it is its own abort; it cannot be batched with the pre-registry problems
|
|
74
|
+
* because a valid operation+client are required even to reach the registry.
|
|
75
|
+
*/
|
|
76
|
+
import type { RawTool, ProtectedTool, EnforcementCoverage, RegistryCoverageReport, ToolBinder, ToolMutationClass } from './tool-registry.js';
|
|
77
|
+
import type { GuardConfig } from './types.js';
|
|
78
|
+
/** Registry config fields callers may forward untouched (S1/S2 do not re-declare them). */
|
|
79
|
+
export type WithCodeRiftsRegistryConfig = {
|
|
80
|
+
unknownToolPolicy?: 'mutating' | 'readonly' | 'reject';
|
|
81
|
+
classify?: Record<string, ToolMutationClass>;
|
|
82
|
+
binders?: Record<string, ToolBinder>;
|
|
83
|
+
forceReadonly?: string[];
|
|
84
|
+
failOnUnguardedMutator?: boolean;
|
|
85
|
+
};
|
|
86
|
+
export type WithCodeRiftsInput = {
|
|
87
|
+
/** Raw tool list handed to the frozen guardToolRegistry (required). */
|
|
88
|
+
tools: RawTool[];
|
|
89
|
+
/** The CodeRifts client guardToolRegistry's config.guard.client expects (required; checked at construction). */
|
|
90
|
+
client: GuardConfig['client'];
|
|
91
|
+
/**
|
|
92
|
+
* REQUIRED session-level operation — no default. Receipts bind to an operation and merge != deploy,
|
|
93
|
+
* so a silent default would risk evaluating a deploy under merge semantics. Governs generic mutating
|
|
94
|
+
* tools; specialised classes keep their operationForClass-derived operation.
|
|
95
|
+
*/
|
|
96
|
+
operation: string;
|
|
97
|
+
/** Optional; carried through untouched. Artifact resolution is a later slice — S1 invents no use for it. */
|
|
98
|
+
repository?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Optional (S2). Minimum REGISTRY-level coverage required at construction. If the registry's actual
|
|
101
|
+
* coverage is weaker than this (by the COVERAGE_STRENGTH ordering), construction ABORTS. It does NOT
|
|
102
|
+
* demand composition-level inescapability (unreachable until S3/S5) — a green construction here is
|
|
103
|
+
* NOT a product-level runtime-enforcement guarantee.
|
|
104
|
+
*/
|
|
105
|
+
requireCoverage?: EnforcementCoverage;
|
|
106
|
+
/** Optional passthrough of existing GuardToolRegistryConfig fields (forwarded as given). */
|
|
107
|
+
registry?: WithCodeRiftsRegistryConfig;
|
|
108
|
+
};
|
|
109
|
+
/** The narrower product-level statement, computed separately from the registry's own report. */
|
|
110
|
+
export type CompositionAssurance = {
|
|
111
|
+
coverage: EnforcementCoverage;
|
|
112
|
+
inescapable_runtime: boolean;
|
|
113
|
+
residuals: string[];
|
|
114
|
+
};
|
|
115
|
+
export type WithCodeRiftsResult = {
|
|
116
|
+
tools: ProtectedTool[];
|
|
117
|
+
/** Untouched RegistryCoverageReport — the registry's own truth (may be COMPLETE / inescapable). */
|
|
118
|
+
registry_report: RegistryCoverageReport;
|
|
119
|
+
/** Product-level assurance — deliberately narrower than the registry. */
|
|
120
|
+
composition_assurance: CompositionAssurance;
|
|
121
|
+
/** Carried through untouched from input when present (no resolution behaviour in S1/S2). */
|
|
122
|
+
repository?: string;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Wrap guardToolRegistry with a mandatory operation and a separately-computed composition assurance.
|
|
126
|
+
* Fails at CONSTRUCTION (never at first tool call) for a missing client, a missing/empty operation, an
|
|
127
|
+
* invalid requireCoverage value, or (S2) an unmet requireCoverage. Registry-thrown construction errors
|
|
128
|
+
* propagate UNCHANGED.
|
|
129
|
+
*/
|
|
130
|
+
export declare function withCodeRifts(input: WithCodeRiftsInput): WithCodeRiftsResult;
|
|
131
|
+
//# sourceMappingURL=with-coderifts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-coderifts.d.ts","sourceRoot":"","sources":["../../src/with-coderifts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AAGH,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EAEtB,UAAU,EACV,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,2FAA2F;AAC3F,MAAM,MAAM,2BAA2B,GAAG;IACxC,iBAAiB,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,uEAAuE;IACvE,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,gHAAgH;IAChH,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC9B;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,4GAA4G;IAC5G,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,2BAA2B,CAAC;CACxC,CAAC;AAEF,gGAAgG;AAChG,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,mGAAmG;IACnG,eAAe,EAAE,sBAAsB,CAAC;IACxC,yEAAyE;IACzE,qBAAqB,EAAE,oBAAoB,CAAC;IAC5C,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAuCF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,CA8F5E"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* withCodeRifts — additive orchestration layer above the frozen security core (slices S1 + S2).
|
|
3
|
+
*
|
|
4
|
+
* S1 DELIVERS: a single entry point that wraps the frozen `guardToolRegistry` with a MANDATORY
|
|
5
|
+
* `operation` and returns, side by side, (a) the registry's own untouched coverage report and (b) a
|
|
6
|
+
* separately-computed, deliberately-narrower composition-level assurance. It performs NO IO — every
|
|
7
|
+
* input is host-supplied, exactly like the primitives it composes.
|
|
8
|
+
*
|
|
9
|
+
* S2 ADDS startup honesty: it refuses to return a result that would LOOK protected while protection is
|
|
10
|
+
* incomplete. Concretely S2 adds — (1) an explicit `unknownToolPolicy` default of 'mutating' when the
|
|
11
|
+
* caller did not specify one, so an unclassified tool is never silently downgraded to readonly;
|
|
12
|
+
* (2) an optional `requireCoverage` that aborts construction when the registry's coverage is weaker
|
|
13
|
+
* than required, by an EXPLICIT strength ordering; (3) weakening-override residuals recorded onto
|
|
14
|
+
* `composition_assurance` so the trace survives into any later coverageReport consumer. S2 adds NO
|
|
15
|
+
* call-time behaviour.
|
|
16
|
+
*
|
|
17
|
+
* S2 does NOT re-guard what the registry already fails closed on. A `forceReadonly`/`classify`
|
|
18
|
+
* downgrade of a heuristic mutator is, under the registry's default `failOnUnguardedMutator:true`,
|
|
19
|
+
* already a thrown `FORCE_READONLY_MUTATOR` (no report exists); when the caller passes
|
|
20
|
+
* `failOnUnguardedMutator:false` it is already registry coverage 'BYPASSED', which fails any
|
|
21
|
+
* `requireCoverage` above BYPASSED by the ordering alone. So there is deliberately NO second
|
|
22
|
+
* composition-level abort for weakening overrides — that would create two places that must agree. The
|
|
23
|
+
* composition merely RECORDS the residual (composition_forced_readonly_on_heuristic_mutator /
|
|
24
|
+
* composition_unknown_treated_as_readonly) so the reason the composition is narrower than it looks is
|
|
25
|
+
* preserved on the assurance object. Registry-thrown errors propagate UNCHANGED (never wrapped or
|
|
26
|
+
* swallowed).
|
|
27
|
+
*
|
|
28
|
+
* How the weakening residuals are derived, and their KNOWN LIMITATION (a frozen-registry property, not
|
|
29
|
+
* a composition property): both residuals are read SOLELY from `registry_report.warnings` — the
|
|
30
|
+
* composition never recomputes classification. The registry emits `force_readonly_on_mutator_heuristic`
|
|
31
|
+
* ONLY for the downgrade of a HEURISTIC-classified mutator (a tool with no `classify` entry and no
|
|
32
|
+
* `mutationClass`, whose NAME heuristic is mutating), reached via `forceReadonly` OR a `classify`
|
|
33
|
+
* entry. Crucially, `forceReadonly` has NO effect on a tool the caller declared with an explicit
|
|
34
|
+
* `mutationClass`: `resolveClass` (tool-registry.ts:154-165) returns on `tool.mutationClass` at :157,
|
|
35
|
+
* BEFORE `forceReadonly` is consulted at :158 — so such a tool resolves `mutating`, stays guarded,
|
|
36
|
+
* yields coverage 'COMPLETE', and emits NO warning. The composition therefore produces NO residual in
|
|
37
|
+
* that case and CANNOT detect it: the caller's `forceReadonly` was silently ignored by the frozen
|
|
38
|
+
* registry. This is a limitation of the frozen registry surface, recorded (not worked around) here and
|
|
39
|
+
* pinned by a "documented limitation" test. The residual is named
|
|
40
|
+
* `composition_forced_readonly_on_heuristic_mutator` so it does not promise coverage of the case it
|
|
41
|
+
* cannot see.
|
|
42
|
+
*
|
|
43
|
+
* STILL DELIBERATELY OUT OF SCOPE (later slices; not stubbed, not implied here): call-time policy,
|
|
44
|
+
* automatic binders, receipt carry-forward, WARN monitoring, framework adapters, artifact resolution.
|
|
45
|
+
*
|
|
46
|
+
* THE TWO-SCOPE RULE (never merged):
|
|
47
|
+
* - `registry_report` is EXACTLY what `guardToolRegistry` returned, passed through untouched. It may
|
|
48
|
+
* legitimately state coverage 'COMPLETE' and claim.inescapable_runtime true — that is the runtime
|
|
49
|
+
* tool-boundary's own honest truth (Placement A).
|
|
50
|
+
* - `composition_assurance` is the narrower PRODUCT-level statement — what withCodeRifts as a whole
|
|
51
|
+
* is willing to claim today. It is computed SEPARATELY and never rewrites the registry's verdict.
|
|
52
|
+
* Its S1 semantics are UNCHANGED in S2: coverage stays 'PARTIAL', inescapable_runtime stays false
|
|
53
|
+
* via the same COMPOSITION_CALL_POLICY_COMPLETE conjunction. S2 only appends residuals.
|
|
54
|
+
* The two truths coexist; neither is derived by mutating the other.
|
|
55
|
+
*
|
|
56
|
+
* `requireCoverage` scope (read this before trusting a green construction): it constrains the
|
|
57
|
+
* REGISTRY-level coverage ONLY. It CANNOT be used to demand composition-level runtime inescapability,
|
|
58
|
+
* because S1's invariant makes that unreachable until S3 (binders) and S5 (receipt carry-forward)
|
|
59
|
+
* land. A caller passing `requireCoverage:'COMPLETE'` is asserting an expectation about the registry
|
|
60
|
+
* tool-boundary surface — NOT a product-level guarantee. The abort text and this doc say so explicitly
|
|
61
|
+
* so nobody reads a green construction as product-level enforcement.
|
|
62
|
+
*
|
|
63
|
+
* Operation semantics (accurate scope): `operationForClass` (tool-registry.ts) already derives a
|
|
64
|
+
* per-tool operation from the tool's mutation class, OVERRIDING the guard config for specialised
|
|
65
|
+
* classes (deploy→'deploy', publish→'publish', vcs-merge→'merge', …). The mandatory `operation` here
|
|
66
|
+
* therefore governs generic mutating tools and acts as the session-level default; it does NOT override
|
|
67
|
+
* a deploy-class tool's 'deploy'. It is mandatory because receipts bind to an operation and
|
|
68
|
+
* merge != deploy: a silent default would risk evaluating a deploy under merge semantics.
|
|
69
|
+
*
|
|
70
|
+
* Abort discipline: pre-registry input problems (missing/empty operation, missing client, an invalid
|
|
71
|
+
* requireCoverage value) are collected and thrown as ONE error listing ALL of them. The
|
|
72
|
+
* requireCoverage-vs-actual check is necessarily sequenced AFTER the registry call (it needs the
|
|
73
|
+
* registry's coverage), so it is its own abort; it cannot be batched with the pre-registry problems
|
|
74
|
+
* because a valid operation+client are required even to reach the registry.
|
|
75
|
+
*/
|
|
76
|
+
import { guardToolRegistry } from './tool-registry.js';
|
|
77
|
+
/**
|
|
78
|
+
* Whether the composition's call-time policy is complete. FALSE in S1/S2: automatic binders (S3) and
|
|
79
|
+
* receipt carry-forward (S5) are not yet delivered, so the composition cannot claim runtime
|
|
80
|
+
* inescapability. Later slices AND additional conjuncts into the composition invariant below rather
|
|
81
|
+
* than replacing this one. UNCHANGED by S2.
|
|
82
|
+
*/
|
|
83
|
+
const COMPOSITION_CALL_POLICY_COMPLETE = false; // becomes true only once S3 (binders) + S5 (receipt carry-forward) land
|
|
84
|
+
const RESIDUAL_CALL_POLICY_INCOMPLETE = 'composition_call_policy_incomplete';
|
|
85
|
+
// S2 weakening-override residuals — derived SOLELY from registry report.warnings (never recomputed).
|
|
86
|
+
// The name says "heuristic_mutator" on purpose: the registry only emits the underlying warning for a
|
|
87
|
+
// downgrade of a HEURISTIC-classified mutator (see the header limitation), so the residual must not
|
|
88
|
+
// promise coverage of a downgrade it cannot see (an explicit-mutationClass tool).
|
|
89
|
+
const RESIDUAL_FORCED_READONLY = 'composition_forced_readonly_on_heuristic_mutator';
|
|
90
|
+
const RESIDUAL_UNKNOWN_READONLY = 'composition_unknown_treated_as_readonly';
|
|
91
|
+
/**
|
|
92
|
+
* Coverage strength ordering, strongest → weakest. `requireCoverage` aborts when the registry's ACTUAL
|
|
93
|
+
* coverage ranks strictly BELOW the required one. COMPLETE is the strongest; BYPASSED and UNKNOWN are
|
|
94
|
+
* the weakest and are unacceptable for any requirement above them. UNKNOWN is the absolute floor
|
|
95
|
+
* (fail-closed: an unobservable coverage never satisfies a requirement above it). Written explicitly
|
|
96
|
+
* so the ordering is not implicit in comparison operators elsewhere.
|
|
97
|
+
*/
|
|
98
|
+
const COVERAGE_STRENGTH = {
|
|
99
|
+
COMPLETE: 3,
|
|
100
|
+
PARTIAL: 2,
|
|
101
|
+
BYPASSED: 1,
|
|
102
|
+
UNKNOWN: 0,
|
|
103
|
+
};
|
|
104
|
+
/** Rank of a coverage string, or undefined for an unrecognised value (fail-closed: caller treats as floor). */
|
|
105
|
+
function coverageRank(coverage) {
|
|
106
|
+
return Object.prototype.hasOwnProperty.call(COVERAGE_STRENGTH, coverage)
|
|
107
|
+
? COVERAGE_STRENGTH[coverage]
|
|
108
|
+
: undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Wrap guardToolRegistry with a mandatory operation and a separately-computed composition assurance.
|
|
112
|
+
* Fails at CONSTRUCTION (never at first tool call) for a missing client, a missing/empty operation, an
|
|
113
|
+
* invalid requireCoverage value, or (S2) an unmet requireCoverage. Registry-thrown construction errors
|
|
114
|
+
* propagate UNCHANGED.
|
|
115
|
+
*/
|
|
116
|
+
export function withCodeRifts(input) {
|
|
117
|
+
if (!input || typeof input !== 'object') {
|
|
118
|
+
throw new Error('withCodeRifts: input object is required');
|
|
119
|
+
}
|
|
120
|
+
// Pre-registry input validation — collect ALL problems, throw once (a valid operation + client are
|
|
121
|
+
// required even to reach the registry, so these cannot be batched with the post-registry check).
|
|
122
|
+
const problems = [];
|
|
123
|
+
if (typeof input.operation !== 'string' || input.operation.trim() === '') {
|
|
124
|
+
problems.push('`operation` is required and must be a non-empty string (receipts bind to an operation; merge != deploy, so there is no safe default)');
|
|
125
|
+
}
|
|
126
|
+
if (input.client == null) {
|
|
127
|
+
problems.push('`client` is required at construction (guardToolRegistry needs config.guard.client to wrap any mutating tool)');
|
|
128
|
+
}
|
|
129
|
+
if (input.requireCoverage !== undefined && coverageRank(input.requireCoverage) === undefined) {
|
|
130
|
+
problems.push(`\`requireCoverage\` must be one of COMPLETE | PARTIAL | BYPASSED | UNKNOWN (got ${JSON.stringify(input.requireCoverage)})`);
|
|
131
|
+
}
|
|
132
|
+
if (problems.length > 0) {
|
|
133
|
+
throw new Error(`withCodeRifts: construction aborted — ${problems.length} condition(s):\n`
|
|
134
|
+
+ problems.map((p) => ` - ${p}`).join('\n'));
|
|
135
|
+
}
|
|
136
|
+
const reg = input.registry ?? {};
|
|
137
|
+
// Defaults, applied ONLY when the caller did not specify them:
|
|
138
|
+
// - unknownToolPolicy → 'mutating': an unclassified tool must never SILENTLY become readonly (that
|
|
139
|
+
// would hide a raw mutating capability behind a green result). The caller may still explicitly pass
|
|
140
|
+
// 'readonly'/'reject'; an explicit 'readonly' that downgrades an unknown tool is surfaced as the
|
|
141
|
+
// composition_unknown_treated_as_readonly residual below.
|
|
142
|
+
// - failOnUnguardedMutator: NOT defaulted here — the registry owns its own default (true). Forward
|
|
143
|
+
// the caller's value if given so there is a single source of truth for it.
|
|
144
|
+
const config = {
|
|
145
|
+
guard: { client: input.client, operation: input.operation },
|
|
146
|
+
unknownToolPolicy: reg.unknownToolPolicy ?? 'mutating',
|
|
147
|
+
classify: reg.classify,
|
|
148
|
+
binders: reg.binders,
|
|
149
|
+
forceReadonly: reg.forceReadonly,
|
|
150
|
+
failOnUnguardedMutator: reg.failOnUnguardedMutator,
|
|
151
|
+
};
|
|
152
|
+
// Registry-thrown construction errors (INVALID_TOOL, DUPLICATE_TOOL_NAME, UNKNOWN_TOOL,
|
|
153
|
+
// FORCE_READONLY_MUTATOR, GUARD_CONFIG_INVALID) propagate UNCHANGED — never caught, wrapped, or
|
|
154
|
+
// swallowed. That is the real contract: the composition does not re-guard what the registry
|
|
155
|
+
// already fails closed on.
|
|
156
|
+
const { tools, report } = guardToolRegistry(input.tools, config);
|
|
157
|
+
// S2 requireCoverage — abort if the REGISTRY-level coverage is weaker than required. This is not a
|
|
158
|
+
// weakening-specific rule: BYPASSED (from a forced downgrade under failOnUnguardedMutator:false) is
|
|
159
|
+
// simply weaker than COMPLETE by the ordering, so it fails here for the same reason PARTIAL does.
|
|
160
|
+
if (input.requireCoverage !== undefined) {
|
|
161
|
+
const requiredRank = coverageRank(input.requireCoverage); // validated non-undefined pre-registry
|
|
162
|
+
const actualRank = coverageRank(report.coverage) ?? -1; // fail-closed: unknown coverage = below any floor
|
|
163
|
+
if (requiredRank !== undefined && actualRank < requiredRank) {
|
|
164
|
+
throw new Error(`withCodeRifts: requireCoverage not met — registry coverage '${report.coverage}' is weaker than required '${input.requireCoverage}' `
|
|
165
|
+
+ `(strength ordering COMPLETE > PARTIAL > BYPASSED > UNKNOWN). requireCoverage constrains the REGISTRY tool-boundary surface ONLY; `
|
|
166
|
+
+ `a green construction here is NOT a product-level runtime-inescapability guarantee — composition_assurance.inescapable_runtime stays false until S3/S5.`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Composition invariant — a conjunction later slices EXTEND (add conjuncts), never replace. Because
|
|
170
|
+
// COMPOSITION_CALL_POLICY_COMPLETE is false, the result is deterministically false regardless of the
|
|
171
|
+
// registry's own (possibly true) inescapable_runtime. UNCHANGED by S2.
|
|
172
|
+
const compositionInescapableRuntime = report.claim.inescapable_runtime && COMPOSITION_CALL_POLICY_COMPLETE;
|
|
173
|
+
// S2 residuals — DERIVED from the registry's own report.warnings, never recomputed. The registry
|
|
174
|
+
// conflates forceReadonly and classify downgrades into one warning string, so this maps to two
|
|
175
|
+
// causes (forced-readonly, unknown→readonly), not three (see recon item c).
|
|
176
|
+
const residuals = [RESIDUAL_CALL_POLICY_INCOMPLETE];
|
|
177
|
+
if (report.warnings.some((w) => w.startsWith('force_readonly_on_mutator_heuristic:'))) {
|
|
178
|
+
residuals.push(RESIDUAL_FORCED_READONLY);
|
|
179
|
+
}
|
|
180
|
+
if (report.warnings.includes('unknown_treated_as_readonly')) {
|
|
181
|
+
residuals.push(RESIDUAL_UNKNOWN_READONLY);
|
|
182
|
+
}
|
|
183
|
+
// 'PARTIAL' from the existing EnforcementCoverage union — never 'COMPLETE' while inescapable_runtime
|
|
184
|
+
// is false (that combination would contradict the registry's own formula). UNCHANGED by S2.
|
|
185
|
+
const composition_assurance = {
|
|
186
|
+
coverage: 'PARTIAL',
|
|
187
|
+
inescapable_runtime: compositionInescapableRuntime,
|
|
188
|
+
residuals,
|
|
189
|
+
};
|
|
190
|
+
const result = {
|
|
191
|
+
tools,
|
|
192
|
+
registry_report: report,
|
|
193
|
+
composition_assurance,
|
|
194
|
+
};
|
|
195
|
+
if (input.repository !== undefined)
|
|
196
|
+
result.repository = input.repository;
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=with-coderifts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-coderifts.js","sourceRoot":"","sources":["../../src/with-coderifts.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderifts/agent-guard",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "Fail-closed guard for AI agent tool calls — preflight contract changes before they execute. Security core frozen (agent-guard-api v1.0); v1.1 adds client-side enforcement: receipt→envelope binding, decision↔action reconciliation, safe_for_agent + degraded fail-closed, and enforced⟺executed (P0 client-enforcement pack).",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|