@coderifts/agent-guard 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/dist/cjs/artifact-resolver.d.ts +6 -0
- package/dist/cjs/artifact-resolver.d.ts.map +1 -1
- package/dist/cjs/artifact-resolver.js +6 -0
- package/dist/cjs/artifact-resolver.js.map +1 -1
- package/dist/cjs/index.d.ts +7 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +11 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/tool-registry.d.ts.map +1 -1
- package/dist/cjs/tool-registry.js +68 -6
- package/dist/cjs/tool-registry.js.map +1 -1
- package/dist/cjs/with-coderifts.d.ts +192 -0
- package/dist/cjs/with-coderifts.d.ts.map +1 -0
- package/dist/cjs/with-coderifts.js +313 -0
- package/dist/cjs/with-coderifts.js.map +1 -0
- package/dist/esm/artifact-resolver.d.ts +6 -0
- package/dist/esm/artifact-resolver.d.ts.map +1 -1
- package/dist/esm/artifact-resolver.js +6 -1
- package/dist/esm/artifact-resolver.js.map +1 -1
- package/dist/esm/index.d.ts +7 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +9 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tool-registry.d.ts.map +1 -1
- package/dist/esm/tool-registry.js +68 -6
- package/dist/esm/tool-registry.js.map +1 -1
- package/dist/esm/with-coderifts.d.ts +192 -0
- package/dist/esm/with-coderifts.d.ts.map +1 -0
- package/dist/esm/with-coderifts.js +310 -0
- package/dist/esm/with-coderifts.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* withCodeRifts — additive orchestration layer above the frozen security core (slices S1 + S2 +
|
|
3
|
+
* composition observation).
|
|
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
|
+
* enforcement at call time.
|
|
17
|
+
*
|
|
18
|
+
* COMPOSITION OBSERVATION (this slice): optional `onEvent` is forwarded UNCHANGED onto the guard
|
|
19
|
+
* config the composition builds (the same surface guardToolRegistry already spreads into
|
|
20
|
+
* guardToolCall). Optional `onOutcome` is a composition-level hook: after each GUARDED tool's
|
|
21
|
+
* execute returns a GuardOutcome, the composition invokes onOutcome then returns that outcome to the
|
|
22
|
+
* host byte-identical. Observation is NOT enforcement — it does not change COMPOSITION_CALL_POLICY_COMPLETE,
|
|
23
|
+
* coverage, residuals, or whether any tool runs.
|
|
24
|
+
*
|
|
25
|
+
* TELEMETRY GAPS (honest boundaries — read before claiming "every mutation was checked"):
|
|
26
|
+
* - onEvent does NOT emit a dedicated event for BLOCK or REQUIRE_APPROVAL; after preflight_result
|
|
27
|
+
* those paths return blocked with no further emit. Those outcomes ARE visible via onOutcome
|
|
28
|
+
* (outcome.executed === false, outcome.verdict.kind === 'BLOCK' | 'APPROVAL'), not via onEvent.
|
|
29
|
+
* - The GuardEvent payload carries no envelope, receipt, or fingerprint — only optional decisionId
|
|
30
|
+
* (and action/cause/signals/…). onOutcome carries the full GuardOutcome, including
|
|
31
|
+
* outcome.verdict.envelope where the frozen path attached one (BLOCK / APPROVAL / ALLOW / MONITOR).
|
|
32
|
+
* - onOutcome fires ONLY for guarded tools (_coderifts.guarded === true). Readonly passthrough
|
|
33
|
+
* tools never enter guardToolCall and produce no GuardOutcome; they are not wrapped and never
|
|
34
|
+
* fire onOutcome.
|
|
35
|
+
* - onEvent is partial even for other branches (e.g. closed-availability stops may emit only
|
|
36
|
+
* preflight_start; the declared type 'execution_skipped' is never emitted by the frozen core).
|
|
37
|
+
* - Neither hook alone is a complete substrate for receipt carry-forward: onEvent lacks the
|
|
38
|
+
* envelope/receipt; onOutcome exposes them when present but does not retain or re-inject them
|
|
39
|
+
* across calls (that is a later slice).
|
|
40
|
+
*
|
|
41
|
+
* S2 does NOT re-guard what the registry already fails closed on. A `forceReadonly`/`classify`
|
|
42
|
+
* downgrade of a heuristic mutator is, under the registry's default `failOnUnguardedMutator:true`,
|
|
43
|
+
* already a thrown `FORCE_READONLY_MUTATOR` (no report exists); when the caller passes
|
|
44
|
+
* `failOnUnguardedMutator:false` it is already registry coverage 'BYPASSED', which fails any
|
|
45
|
+
* `requireCoverage` above BYPASSED by the ordering alone. So there is deliberately NO second
|
|
46
|
+
* composition-level abort for weakening overrides — that would create two places that must agree. The
|
|
47
|
+
* composition merely RECORDS the residual (composition_forced_readonly_on_heuristic_mutator /
|
|
48
|
+
* composition_unknown_treated_as_readonly) so the reason the composition is narrower than it looks is
|
|
49
|
+
* preserved on the assurance object. Registry-thrown errors propagate UNCHANGED (never wrapped or
|
|
50
|
+
* swallowed).
|
|
51
|
+
*
|
|
52
|
+
* How the weakening residuals are derived, and their KNOWN LIMITATION (a frozen-registry property, not
|
|
53
|
+
* a composition property): both residuals are read SOLELY from `registry_report.warnings` — the
|
|
54
|
+
* composition never recomputes classification. The registry emits `force_readonly_on_mutator_heuristic`
|
|
55
|
+
* ONLY for the downgrade of a HEURISTIC-classified mutator (a tool with no `classify` entry and no
|
|
56
|
+
* `mutationClass`, whose NAME heuristic is mutating), reached via `forceReadonly` OR a `classify`
|
|
57
|
+
* entry. Crucially, `forceReadonly` has NO effect on a tool the caller declared with an explicit
|
|
58
|
+
* `mutationClass`: `resolveClass` (tool-registry.ts:154-165) returns on `tool.mutationClass` at :157,
|
|
59
|
+
* BEFORE `forceReadonly` is consulted at :158 — so such a tool resolves `mutating`, stays guarded,
|
|
60
|
+
* yields coverage 'COMPLETE', and emits NO warning. The composition therefore produces NO residual in
|
|
61
|
+
* that case and CANNOT detect it: the caller's `forceReadonly` was silently ignored by the frozen
|
|
62
|
+
* registry. This is a limitation of the frozen registry surface, recorded (not worked around) here and
|
|
63
|
+
* pinned by a "documented limitation" test. The residual is named
|
|
64
|
+
* `composition_forced_readonly_on_heuristic_mutator` so it does not promise coverage of the case it
|
|
65
|
+
* cannot see.
|
|
66
|
+
*
|
|
67
|
+
* COMPOSITION_CALL_POLICY_COMPLETE (when the composition may claim product-level runtime
|
|
68
|
+
* inescapability) requires ALL of the following — do not flip the constant when only a subset lands:
|
|
69
|
+
* (1) Automatic binders for call shapes that ALREADY carry both sides of the change (old_string/
|
|
70
|
+
* new_string, edits[]) — DONE (commit 582504a / defaultBinder lift). Not the same as covering
|
|
71
|
+
* every real agent edit shape.
|
|
72
|
+
* (2) Receipt carry-forward (S5) — NOT done.
|
|
73
|
+
* (3) A freshness-safe source of prior content for write-style calls (path + new content only),
|
|
74
|
+
* where the host never supplied `before` — NOT done. The package performs no IO, so a prior it
|
|
75
|
+
* was not given cannot be obtained; inventing one (including empty-string before) would send a
|
|
76
|
+
* fabricated artifact to the oracle. That needs a host/guard freshness protocol, not another
|
|
77
|
+
* binder rename. Flipping this constant on (1)+(2) alone is incorrect.
|
|
78
|
+
*
|
|
79
|
+
* STILL DELIBERATELY OUT OF SCOPE (later slices; not stubbed, not implied here): receipt carry-forward;
|
|
80
|
+
* freshness-safe prior content for write-style calls; call-time STOP re-implementation (already
|
|
81
|
+
* complete in the frozen guardToolCall path); WARN monitoring policy beyond the frozen sink gate;
|
|
82
|
+
* framework adapters. (Both-sides edit-side lifting in defaultBinder is landed — see (1) above.)
|
|
83
|
+
*
|
|
84
|
+
* THE TWO-SCOPE RULE (never merged):
|
|
85
|
+
* - `registry_report` is EXACTLY what `guardToolRegistry` returned, passed through untouched. It may
|
|
86
|
+
* legitimately state coverage 'COMPLETE' and claim.inescapable_runtime true — that is the runtime
|
|
87
|
+
* tool-boundary's own honest truth (Placement A).
|
|
88
|
+
* - `composition_assurance` is the narrower PRODUCT-level statement — what withCodeRifts as a whole
|
|
89
|
+
* is willing to claim today. It is computed SEPARATELY and never rewrites the registry's verdict.
|
|
90
|
+
* Its S1 semantics are UNCHANGED in S2 and under observation: coverage stays 'PARTIAL',
|
|
91
|
+
* inescapable_runtime stays false via the same COMPOSITION_CALL_POLICY_COMPLETE conjunction.
|
|
92
|
+
* The two truths coexist; neither is derived by mutating the other.
|
|
93
|
+
*
|
|
94
|
+
* `requireCoverage` scope (read this before trusting a green construction): it constrains the
|
|
95
|
+
* REGISTRY-level coverage ONLY. It CANNOT be used to demand composition-level runtime inescapability —
|
|
96
|
+
* that remains unreachable while COMPOSITION_CALL_POLICY_COMPLETE is false (see the three conditions
|
|
97
|
+
* above, not merely "binders + receipts"). A caller passing `requireCoverage:'COMPLETE'` is asserting
|
|
98
|
+
* an expectation about the registry tool-boundary surface — NOT a product-level guarantee. The abort
|
|
99
|
+
* text and this doc say so explicitly so nobody reads a green construction as product-level
|
|
100
|
+
* enforcement.
|
|
101
|
+
*
|
|
102
|
+
* Operation semantics (accurate scope): `operationForClass` (tool-registry.ts) already derives a
|
|
103
|
+
* per-tool operation from the tool's mutation class, OVERRIDING the guard config for specialised
|
|
104
|
+
* classes (deploy→'deploy', publish→'publish', vcs-merge→'merge', …). The mandatory `operation` here
|
|
105
|
+
* therefore governs generic mutating tools and acts as the session-level default; it does NOT override
|
|
106
|
+
* a deploy-class tool's 'deploy'. It is mandatory because receipts bind to an operation and
|
|
107
|
+
* merge != deploy: a silent default would risk evaluating a deploy under merge semantics.
|
|
108
|
+
*
|
|
109
|
+
* Abort discipline: pre-registry input problems (missing/empty operation, missing client, an invalid
|
|
110
|
+
* requireCoverage value) are collected and thrown as ONE error listing ALL of them. The
|
|
111
|
+
* requireCoverage-vs-actual check is necessarily sequenced AFTER the registry call (it needs the
|
|
112
|
+
* registry's coverage), so it is its own abort; it cannot be batched with the pre-registry problems
|
|
113
|
+
* because a valid operation+client are required even to reach the registry.
|
|
114
|
+
*/
|
|
115
|
+
import { guardToolRegistry } from './tool-registry.js';
|
|
116
|
+
/**
|
|
117
|
+
* Whether the composition's call-time / content policy is complete enough to claim product-level
|
|
118
|
+
* runtime inescapability. Stays FALSE until EVERY condition below is delivered — later slices ADD
|
|
119
|
+
* conjuncts to the invariant that uses this flag; they must not replace a narrower false narrative.
|
|
120
|
+
*
|
|
121
|
+
* Conditions (all required; do NOT flip this constant because a subset landed):
|
|
122
|
+
* (1) DONE — automatic binders for shapes that already carry both edit sides (old_string/new_string,
|
|
123
|
+
* edits[] → artifacts). Commit 582504a. Does NOT cover write-style path+new-content-only calls.
|
|
124
|
+
* (2) NOT DONE — receipt carry-forward (S5).
|
|
125
|
+
* (3) NOT DONE — freshness-safe prior content for write-style calls. The package does no IO, so a
|
|
126
|
+
* `before` the host never supplied cannot be obtained; inventing one (including empty string)
|
|
127
|
+
* fabricates an oracle input. Needs a host/guard freshness protocol, not binder renaming.
|
|
128
|
+
*
|
|
129
|
+
* Historically this comment named only (1) and (2). That wording is narrower than reality: after (1)
|
|
130
|
+
* shipped, a reader could think "binders + S5" was enough and flip the flag incorrectly. Do not.
|
|
131
|
+
*
|
|
132
|
+
* UNCHANGED by S2 / composition observation (observing ≠ enforcing). Value stays false until all three.
|
|
133
|
+
*/
|
|
134
|
+
const COMPOSITION_CALL_POLICY_COMPLETE = false;
|
|
135
|
+
const RESIDUAL_CALL_POLICY_INCOMPLETE = 'composition_call_policy_incomplete';
|
|
136
|
+
// S2 weakening-override residuals — derived SOLELY from registry report.warnings (never recomputed).
|
|
137
|
+
// The name says "heuristic_mutator" on purpose: the registry only emits the underlying warning for a
|
|
138
|
+
// downgrade of a HEURISTIC-classified mutator (see the header limitation), so the residual must not
|
|
139
|
+
// promise coverage of a downgrade it cannot see (an explicit-mutationClass tool).
|
|
140
|
+
const RESIDUAL_FORCED_READONLY = 'composition_forced_readonly_on_heuristic_mutator';
|
|
141
|
+
const RESIDUAL_UNKNOWN_READONLY = 'composition_unknown_treated_as_readonly';
|
|
142
|
+
/**
|
|
143
|
+
* Coverage strength ordering, strongest → weakest. `requireCoverage` aborts when the registry's ACTUAL
|
|
144
|
+
* coverage ranks strictly BELOW the required one. COMPLETE is the strongest; BYPASSED and UNKNOWN are
|
|
145
|
+
* the weakest and are unacceptable for any requirement above them. UNKNOWN is the absolute floor
|
|
146
|
+
* (fail-closed: an unobservable coverage never satisfies a requirement above it). Written explicitly
|
|
147
|
+
* so the ordering is not implicit in comparison operators elsewhere.
|
|
148
|
+
*/
|
|
149
|
+
const COVERAGE_STRENGTH = {
|
|
150
|
+
COMPLETE: 3,
|
|
151
|
+
PARTIAL: 2,
|
|
152
|
+
BYPASSED: 1,
|
|
153
|
+
UNKNOWN: 0,
|
|
154
|
+
};
|
|
155
|
+
/** Rank of a coverage string, or undefined for an unrecognised value (fail-closed: caller treats as floor). */
|
|
156
|
+
function coverageRank(coverage) {
|
|
157
|
+
return Object.prototype.hasOwnProperty.call(COVERAGE_STRENGTH, coverage)
|
|
158
|
+
? COVERAGE_STRENGTH[coverage]
|
|
159
|
+
: undefined;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Invoke onOutcome without affecting the host call. Swallows synchronous throws AND rejected
|
|
163
|
+
* promises (unlike the frozen emit hook, which only try/catches sync throws and ignores returned
|
|
164
|
+
* promises — that host-side footgun is deliberately not reproduced here).
|
|
165
|
+
*/
|
|
166
|
+
async function safeOnOutcome(onOutcome, payload) {
|
|
167
|
+
try {
|
|
168
|
+
await Promise.resolve(onOutcome(payload));
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
/* observation never changes execution */
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Build a NEW ProtectedTool shell around a frozen guarded tool so we can replace execute without
|
|
176
|
+
* mutating the frozen registry object. name / description / inputSchema / meta / _coderifts are
|
|
177
|
+
* copied by reference (the _coderifts bag is already frozen by the registry). The new shell is
|
|
178
|
+
* frozen for parity with registry tools.
|
|
179
|
+
*/
|
|
180
|
+
function wrapGuardedForObservation(tool, onOutcome) {
|
|
181
|
+
const innerExecute = tool.execute;
|
|
182
|
+
const toolName = tool.name;
|
|
183
|
+
const shell = {
|
|
184
|
+
name: tool.name,
|
|
185
|
+
description: tool.description,
|
|
186
|
+
inputSchema: tool.inputSchema,
|
|
187
|
+
meta: tool.meta,
|
|
188
|
+
_coderifts: tool._coderifts,
|
|
189
|
+
execute: async (args) => {
|
|
190
|
+
// If the inner execute rejects, do NOT invent an outcome — propagate the rejection unchanged.
|
|
191
|
+
const outcome = await innerExecute(args);
|
|
192
|
+
await safeOnOutcome(onOutcome, {
|
|
193
|
+
toolName,
|
|
194
|
+
// Guarded execute always returns a GuardOutcome from guardToolCall; assert the type for callers.
|
|
195
|
+
outcome: outcome,
|
|
196
|
+
});
|
|
197
|
+
// Host must receive the same object the unwrapped tool returned (reference-identical).
|
|
198
|
+
return outcome;
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
// Match registry freeze discipline: freeze _coderifts if not already, freeze the shell.
|
|
202
|
+
if (!Object.isFrozen(shell._coderifts))
|
|
203
|
+
Object.freeze(shell._coderifts);
|
|
204
|
+
return Object.freeze(shell);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Wrap guardToolRegistry with a mandatory operation and a separately-computed composition assurance.
|
|
208
|
+
* Fails at CONSTRUCTION (never at first tool call) for a missing client, a missing/empty operation, an
|
|
209
|
+
* invalid requireCoverage value, or (S2) an unmet requireCoverage. Registry-thrown construction errors
|
|
210
|
+
* propagate UNCHANGED.
|
|
211
|
+
*/
|
|
212
|
+
export function withCodeRifts(input) {
|
|
213
|
+
if (!input || typeof input !== 'object') {
|
|
214
|
+
throw new Error('withCodeRifts: input object is required');
|
|
215
|
+
}
|
|
216
|
+
// Pre-registry input validation — collect ALL problems, throw once (a valid operation + client are
|
|
217
|
+
// required even to reach the registry, so these cannot be batched with the post-registry check).
|
|
218
|
+
const problems = [];
|
|
219
|
+
if (typeof input.operation !== 'string' || input.operation.trim() === '') {
|
|
220
|
+
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)');
|
|
221
|
+
}
|
|
222
|
+
if (input.client == null) {
|
|
223
|
+
problems.push('`client` is required at construction (guardToolRegistry needs config.guard.client to wrap any mutating tool)');
|
|
224
|
+
}
|
|
225
|
+
if (input.requireCoverage !== undefined && coverageRank(input.requireCoverage) === undefined) {
|
|
226
|
+
problems.push(`\`requireCoverage\` must be one of COMPLETE | PARTIAL | BYPASSED | UNKNOWN (got ${JSON.stringify(input.requireCoverage)})`);
|
|
227
|
+
}
|
|
228
|
+
if (problems.length > 0) {
|
|
229
|
+
throw new Error(`withCodeRifts: construction aborted — ${problems.length} condition(s):\n`
|
|
230
|
+
+ problems.map((p) => ` - ${p}`).join('\n'));
|
|
231
|
+
}
|
|
232
|
+
const reg = input.registry ?? {};
|
|
233
|
+
// Defaults, applied ONLY when the caller did not specify them:
|
|
234
|
+
// - unknownToolPolicy → 'mutating': an unclassified tool must never SILENTLY become readonly (that
|
|
235
|
+
// would hide a raw mutating capability behind a green result). The caller may still explicitly pass
|
|
236
|
+
// 'readonly'/'reject'; an explicit 'readonly' that downgrades an unknown tool is surfaced as the
|
|
237
|
+
// composition_unknown_treated_as_readonly residual below.
|
|
238
|
+
// - failOnUnguardedMutator: NOT defaulted here — the registry owns its own default (true). Forward
|
|
239
|
+
// the caller's value if given so there is a single source of truth for it.
|
|
240
|
+
// Guard config: client + operation always; onEvent forwarded UNCHANGED when provided (no second
|
|
241
|
+
// try/catch layer — frozen emit already swallows sync throws).
|
|
242
|
+
const guard = { client: input.client, operation: input.operation };
|
|
243
|
+
if (input.onEvent !== undefined) {
|
|
244
|
+
guard.onEvent = input.onEvent;
|
|
245
|
+
}
|
|
246
|
+
const config = {
|
|
247
|
+
guard,
|
|
248
|
+
unknownToolPolicy: reg.unknownToolPolicy ?? 'mutating',
|
|
249
|
+
classify: reg.classify,
|
|
250
|
+
binders: reg.binders,
|
|
251
|
+
forceReadonly: reg.forceReadonly,
|
|
252
|
+
failOnUnguardedMutator: reg.failOnUnguardedMutator,
|
|
253
|
+
};
|
|
254
|
+
// Registry-thrown construction errors (INVALID_TOOL, DUPLICATE_TOOL_NAME, UNKNOWN_TOOL,
|
|
255
|
+
// FORCE_READONLY_MUTATOR, GUARD_CONFIG_INVALID) propagate UNCHANGED — never caught, wrapped, or
|
|
256
|
+
// swallowed. That is the real contract: the composition does not re-guard what the registry
|
|
257
|
+
// already fails closed on.
|
|
258
|
+
const { tools, report } = guardToolRegistry(input.tools, config);
|
|
259
|
+
// S2 requireCoverage — abort if the REGISTRY-level coverage is weaker than required. This is not a
|
|
260
|
+
// weakening-specific rule: BYPASSED (from a forced downgrade under failOnUnguardedMutator:false) is
|
|
261
|
+
// simply weaker than COMPLETE by the ordering, so it fails here for the same reason PARTIAL does.
|
|
262
|
+
if (input.requireCoverage !== undefined) {
|
|
263
|
+
const requiredRank = coverageRank(input.requireCoverage); // validated non-undefined pre-registry
|
|
264
|
+
const actualRank = coverageRank(report.coverage) ?? -1; // fail-closed: unknown coverage = below any floor
|
|
265
|
+
if (requiredRank !== undefined && actualRank < requiredRank) {
|
|
266
|
+
throw new Error(`withCodeRifts: requireCoverage not met — registry coverage '${report.coverage}' is weaker than required '${input.requireCoverage}' `
|
|
267
|
+
+ `(strength ordering COMPLETE > PARTIAL > BYPASSED > UNKNOWN). requireCoverage constrains the REGISTRY tool-boundary surface ONLY; `
|
|
268
|
+
+ `a green construction here is NOT a product-level runtime-inescapability guarantee — composition_assurance.inescapable_runtime stays false until receipt carry-forward and a freshness-safe prior for write-style calls land.`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Composition invariant — a conjunction later slices EXTEND (add conjuncts), never replace. Because
|
|
272
|
+
// COMPOSITION_CALL_POLICY_COMPLETE is false, the result is deterministically false regardless of the
|
|
273
|
+
// registry's own (possibly true) inescapable_runtime. UNCHANGED by S2. UNCHANGED by observation.
|
|
274
|
+
const compositionInescapableRuntime = report.claim.inescapable_runtime && COMPOSITION_CALL_POLICY_COMPLETE;
|
|
275
|
+
// S2 residuals — DERIVED from the registry's own report.warnings, never recomputed. The registry
|
|
276
|
+
// conflates forceReadonly and classify downgrades into one warning string, so this maps to two
|
|
277
|
+
// causes (forced-readonly, unknown→readonly), not three (see recon item c).
|
|
278
|
+
const residuals = [RESIDUAL_CALL_POLICY_INCOMPLETE];
|
|
279
|
+
if (report.warnings.some((w) => w.startsWith('force_readonly_on_mutator_heuristic:'))) {
|
|
280
|
+
residuals.push(RESIDUAL_FORCED_READONLY);
|
|
281
|
+
}
|
|
282
|
+
if (report.warnings.includes('unknown_treated_as_readonly')) {
|
|
283
|
+
residuals.push(RESIDUAL_UNKNOWN_READONLY);
|
|
284
|
+
}
|
|
285
|
+
// 'PARTIAL' from the existing EnforcementCoverage union — never 'COMPLETE' while inescapable_runtime
|
|
286
|
+
// is false (that combination would contradict the registry's own formula). UNCHANGED by S2 / observation.
|
|
287
|
+
const composition_assurance = {
|
|
288
|
+
coverage: 'PARTIAL',
|
|
289
|
+
inescapable_runtime: compositionInescapableRuntime,
|
|
290
|
+
residuals,
|
|
291
|
+
};
|
|
292
|
+
// Outcome observation: registry returns FROZEN ProtectedTool objects (and a frozen tools array).
|
|
293
|
+
// We cannot reassign execute on a frozen tool, so we build NEW shells for guarded tools only when
|
|
294
|
+
// onOutcome is provided. Readonly tools are left as the same object references (no GuardOutcome).
|
|
295
|
+
// The tools ARRAY is also frozen by the registry — always return a fresh array when we wrap.
|
|
296
|
+
let toolsOut = tools;
|
|
297
|
+
if (input.onOutcome) {
|
|
298
|
+
const onOutcome = input.onOutcome;
|
|
299
|
+
toolsOut = Object.freeze(tools.map((t) => (t._coderifts.guarded ? wrapGuardedForObservation(t, onOutcome) : t)));
|
|
300
|
+
}
|
|
301
|
+
const result = {
|
|
302
|
+
tools: toolsOut,
|
|
303
|
+
registry_report: report,
|
|
304
|
+
composition_assurance,
|
|
305
|
+
};
|
|
306
|
+
if (input.repository !== undefined)
|
|
307
|
+
result.repository = input.repository;
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
//# 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.
|
|
3
|
+
"version": "1.7.0",
|
|
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",
|