@sema-agent/core 6.0.0 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/agents/launch-receipt-contract.d.ts +34 -0
- package/dist/agents/launch-receipt-contract.js +5 -0
- package/dist/agents/subagent.d.ts +134 -2
- package/dist/agents/subagent.js +132 -31
- package/dist/core/file-history-store.js +24 -2
- package/dist/core/governance-codes.d.ts +11 -2
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/permission-rule-consent.d.ts +42 -2
- package/dist/core/permission-rule-consent.js +93 -11
- package/dist/core/permission-rule-model.d.ts +51 -9
- package/dist/core/permission-rule-model.js +4 -2
- package/dist/core/permission-rule-session.d.ts +124 -0
- package/dist/core/permission-rule-session.js +121 -0
- package/dist/core/permission-rule-store.d.ts +65 -2
- package/dist/core/permission-rule-store.js +60 -6
- package/dist/core/permission-rule-sync.d.ts +9 -0
- package/dist/core/permission-rule-sync.js +37 -8
- package/dist/core/roles.js +1 -1
- package/dist/core/runner/prepare-task.js +35 -2
- package/dist/core/runner/runtask.js +2 -0
- package/dist/core/store-contracts/permission-rule-sync-contract.js +15 -1
- package/dist/core/task-notification.d.ts +20 -0
- package/dist/core/trace.d.ts +7 -2
- package/dist/core/types.d.ts +85 -1
- package/dist/core/wiring-manifest.d.ts +18 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/orchestration/run-workflow-tool.js +2 -2
- package/dist/orchestration/workflow.js +18 -10
- package/dist/stores/file/permission-rule-store.d.ts +11 -0
- package/dist/stores/file/permission-rule-store.js +22 -9
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +15 -1
|
@@ -10,7 +10,11 @@ export function writerOf(store) {
|
|
|
10
10
|
return w !== undefined && typeof w.apply === "function" && typeof w.nextDot === "function" ? w : undefined;
|
|
11
11
|
}
|
|
12
12
|
export function sameScope(a, b) {
|
|
13
|
-
|
|
13
|
+
if (a.kind === "global")
|
|
14
|
+
return b.kind === "global";
|
|
15
|
+
if (a.kind === "session")
|
|
16
|
+
return b.kind === "session" && a.sessionId === b.sessionId;
|
|
17
|
+
return b.kind === "project" && a.root === b.root;
|
|
14
18
|
}
|
|
15
19
|
function sameDot(a, b) {
|
|
16
20
|
return a.actor === b.actor && a.counter === b.counter;
|
|
@@ -47,7 +51,29 @@ export function assertDeleteDeltaCarriesNoAdd(delta) {
|
|
|
47
51
|
throw new Error("a tighten-delete must carry a tombstone naming at least one observed add dot");
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
|
-
function
|
|
54
|
+
export function assertWriteDeltaScopeDurable(delta) {
|
|
55
|
+
const scope = delta.kind === "redemption-add" ? delta.scope : delta.tombstone?.scope;
|
|
56
|
+
if (scope?.kind === "session") {
|
|
57
|
+
const e = new Error(delta.kind === "redemption-add"
|
|
58
|
+
? "a session-scope rule cannot enter the persisted store — its home is the session's own overlay (design/382 §4.3); refusing the redemption-add"
|
|
59
|
+
: "a session-scope tombstone cannot enter the persisted store — session rows are not individually deletable and die with their session (design/382 §4.3/§10); refusing the tighten-delete");
|
|
60
|
+
e.code = "unsupported.session_scope_store";
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
if (!isValidDurableScope(scope)) {
|
|
64
|
+
throw new Error(delta.kind === "redemption-add"
|
|
65
|
+
? "a redemption-add whose scope is not a well-formed durable scope cannot enter the persisted store (design/382 §4.3: the face is {global, project-with-a-root}) — refusing the write"
|
|
66
|
+
:
|
|
67
|
+
"a tighten-delete must carry a tombstone whose scope is a well-formed durable scope (design/382 §4.3: the face is {global, project-with-a-root}) — refusing the write");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function isValidConsentScope(scope) {
|
|
71
|
+
const sc = scope;
|
|
72
|
+
return (sc?.kind === "global" ||
|
|
73
|
+
(sc?.kind === "project" && typeof sc.root === "string" && sc.root !== "") ||
|
|
74
|
+
(sc?.kind === "session" && typeof sc.sessionId === "string" && sc.sessionId !== ""));
|
|
75
|
+
}
|
|
76
|
+
export function isValidDurableScope(scope) {
|
|
51
77
|
const sc = scope;
|
|
52
78
|
return sc?.kind === "global" || (sc?.kind === "project" && typeof sc.root === "string" && sc.root !== "");
|
|
53
79
|
}
|
|
@@ -56,7 +82,7 @@ function isValidDot(dot) {
|
|
|
56
82
|
return typeof d?.actor === "string" && d.actor !== "" && typeof d.counter === "number" && Number.isFinite(d.counter);
|
|
57
83
|
}
|
|
58
84
|
function scopeKey(scope) {
|
|
59
|
-
return JSON.stringify(scope.kind === "global" ? ["g"] : ["p", scope.root]);
|
|
85
|
+
return JSON.stringify(scope.kind === "global" ? ["g"] : scope.kind === "session" ? ["s", scope.sessionId] : ["p", scope.root]);
|
|
60
86
|
}
|
|
61
87
|
function dotKey(dot) {
|
|
62
88
|
return JSON.stringify([dot.actor, dot.counter]);
|
|
@@ -114,6 +140,11 @@ export function screenRuleSyncState(state) {
|
|
|
114
140
|
for (const r of state.rules) {
|
|
115
141
|
if (!Array.isArray(r.adds) || r.adds.length === 0)
|
|
116
142
|
continue;
|
|
143
|
+
if (r.scope?.kind === "session") {
|
|
144
|
+
for (const a of r.adds)
|
|
145
|
+
rejected.push({ rule: r.rule, scope: r.scope, dot: a.dot, reason: "session_scope_not_durable" });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
117
148
|
const n = normalizePersistedRule(r);
|
|
118
149
|
if ("reject" in n) {
|
|
119
150
|
for (const a of r.adds)
|
|
@@ -122,7 +153,15 @@ export function screenRuleSyncState(state) {
|
|
|
122
153
|
}
|
|
123
154
|
rules.push(r);
|
|
124
155
|
}
|
|
125
|
-
|
|
156
|
+
const tombstones = [];
|
|
157
|
+
for (const t of state.tombstones) {
|
|
158
|
+
if (t?.scope?.kind === "session") {
|
|
159
|
+
rejected.push({ rule: t.rule, scope: t.scope, dot: t.deletedBy, reason: "session_scope_not_durable" });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
tombstones.push(t);
|
|
163
|
+
}
|
|
164
|
+
return { state: { rules, tombstones }, rejected };
|
|
126
165
|
}
|
|
127
166
|
export function joinRuleStates(a, b) {
|
|
128
167
|
const sa = screenRuleSyncState(a).state;
|
|
@@ -183,10 +222,12 @@ export function applySyncJoin(cur, delta, nowMs) {
|
|
|
183
222
|
throw new Error("a sync-join delta must carry an inbound state with rules and tombstones arrays");
|
|
184
223
|
}
|
|
185
224
|
for (const t of delta.inbound.tombstones) {
|
|
225
|
+
if (t?.scope?.kind === "session")
|
|
226
|
+
continue;
|
|
186
227
|
if (!Array.isArray(t?.removedDots) || t.removedDots.length === 0) {
|
|
187
228
|
throw new Error("an inbound tombstone must name at least one observed add dot — refusing the whole sync-join (the client's partitioning must withhold the round's adds alongside a malformed tombstone)");
|
|
188
229
|
}
|
|
189
|
-
if (!
|
|
230
|
+
if (!isValidDurableScope(t.scope) || !isValidDot(t.deletedBy) || !t.removedDots.every(isValidDot)) {
|
|
190
231
|
throw new Error("an inbound tombstone carries a malformed scope or dot — refusing the whole sync-join (a tombstone that cannot identity-match what it deletes would land its covered adds live)");
|
|
191
232
|
}
|
|
192
233
|
const canonicalTomb = parseAllowRuleText(t.rule);
|
|
@@ -229,7 +270,7 @@ export function applySyncJoin(cur, delta, nowMs) {
|
|
|
229
270
|
withheldAdds += r.adds.length;
|
|
230
271
|
continue;
|
|
231
272
|
}
|
|
232
|
-
if (!
|
|
273
|
+
if (!isValidDurableScope(r.scope)) {
|
|
233
274
|
for (const a of r.adds)
|
|
234
275
|
droppedInbound.push({ rule: r.rule, scope: r.scope, dot: a.dot, reason: "metadata_mismatch" });
|
|
235
276
|
continue;
|
|
@@ -288,6 +329,11 @@ export function applySyncJoin(cur, delta, nowMs) {
|
|
|
288
329
|
}
|
|
289
330
|
}
|
|
290
331
|
for (const q of delta.quarantine ?? []) {
|
|
332
|
+
if (q.scope?.kind === "session") {
|
|
333
|
+
const e = new Error("a quarantine instruction names a session scope — the persisted store never holds a session row (design/382 §4.3); refusing the instruction");
|
|
334
|
+
e.code = "unsupported.session_scope_store";
|
|
335
|
+
throw e;
|
|
336
|
+
}
|
|
291
337
|
for (const dot of q.dots) {
|
|
292
338
|
if (joined.tombstones.some((t) => sameDot(t.deletedBy, dot))) {
|
|
293
339
|
throw new Error("a quarantine instruction may not touch a tombstone — it moves adds only");
|
|
@@ -332,6 +378,12 @@ export async function removePersistedRule(opts) {
|
|
|
332
378
|
const target = opts.principal;
|
|
333
379
|
const owner = typeof target === "string" ? { kind: "principal", principal: target } : structuredClone(target);
|
|
334
380
|
const provider = opts.provider;
|
|
381
|
+
if (scope?.kind === "session") {
|
|
382
|
+
return {
|
|
383
|
+
status: "failed",
|
|
384
|
+
error: "unsupported.session_scope_store: a session-scope rule cannot be removed through the persisted-store entry — session authorizations live in the session's own overlay and end with the session (design/382 §4.3)",
|
|
385
|
+
};
|
|
386
|
+
}
|
|
335
387
|
if (owner.kind === "local-owner" && provider.forLocalOwner === undefined) {
|
|
336
388
|
return { status: "failed", error: "this provider has no local-owner bucket (forLocalOwner is not implemented) — a local-owner rule cannot be removed through it" };
|
|
337
389
|
}
|
|
@@ -435,10 +487,12 @@ export class InMemoryPermissionRuleStore {
|
|
|
435
487
|
if (opts.expectedRev !== this.rev)
|
|
436
488
|
return { conflict: true, rev: this.rev };
|
|
437
489
|
if (delta.kind === "redemption-add") {
|
|
490
|
+
assertWriteDeltaScopeDurable(delta);
|
|
438
491
|
assertRedemptionNotQuarantined(this.quarantinedRows, delta);
|
|
439
492
|
this.rules = foldDelta(this.rules, delta);
|
|
440
493
|
}
|
|
441
494
|
else if (delta.kind === "tighten-delete") {
|
|
495
|
+
assertWriteDeltaScopeDurable(delta);
|
|
442
496
|
assertDeleteDeltaCarriesNoAdd(delta);
|
|
443
497
|
this.tombstones = [...this.tombstones, delta.tombstone];
|
|
444
498
|
}
|
|
@@ -134,6 +134,15 @@ interface ParsedRuleSyncResponse {
|
|
|
134
134
|
dot: RuleDot;
|
|
135
135
|
code: RuleRejectCode;
|
|
136
136
|
}>;
|
|
137
|
+
/** design/382 §4.3 — SESSION-scope tombstones, dropped at the parse as per-row refusals (never
|
|
138
|
+
* `undefined`-malformed): a session row can never land regardless of its dots, so classifying a
|
|
139
|
+
* DAMAGED one as generic-malformed would withhold every valid inbound add on every round — one
|
|
140
|
+
* peer row poisoning sync forever, the exact shape the per-row drop arm exists to avoid. */
|
|
141
|
+
sessionScopeTombstones: Array<{
|
|
142
|
+
rule: string;
|
|
143
|
+
scope: RuleScope;
|
|
144
|
+
dot: RuleDot;
|
|
145
|
+
}>;
|
|
137
146
|
warnings: string[];
|
|
138
147
|
}
|
|
139
148
|
/**
|
|
@@ -91,6 +91,9 @@ export async function syncPermissionRules(opts) {
|
|
|
91
91
|
for (const t of response.textRefusedTombstones) {
|
|
92
92
|
dropped.push({ rule: t.rule, scope: t.scope, dot: t.dot, reason: "invalid_rule_text" });
|
|
93
93
|
}
|
|
94
|
+
for (const t of response.sessionScopeTombstones) {
|
|
95
|
+
dropped.push({ rule: t.rule, scope: t.scope, dot: t.dot, reason: "session_scope_not_durable" });
|
|
96
|
+
}
|
|
94
97
|
if (response.textRefusedTombstones.length > 0) {
|
|
95
98
|
const named = response.textRefusedTombstones
|
|
96
99
|
.slice(0, DISCLOSED_REFUSED_TOMBSTONES)
|
|
@@ -131,6 +134,10 @@ export async function syncPermissionRules(opts) {
|
|
|
131
134
|
}
|
|
132
135
|
const localDots = new Set(raw.rules.flatMap((r) => r.adds.map((a) => JSON.stringify([a.dot.actor, a.dot.counter]))));
|
|
133
136
|
for (const d of response.dropped) {
|
|
137
|
+
if (d.scope.kind === "session") {
|
|
138
|
+
warnings.push(`the server's dropped list names a session-scope row (${escapeForDisclosure(d.rule)}) — the durable store never holds one, so there is no local row it could mean; ignored`);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
134
141
|
if (localDots.has(JSON.stringify([d.dot.actor, d.dot.counter]))) {
|
|
135
142
|
const reason = d.reason === "below_gc_frontier" ? "below_gc_frontier" : "server_rejected";
|
|
136
143
|
quarantine.push({ rule: d.rule, scope: d.scope, dots: [d.dot], reason });
|
|
@@ -190,7 +197,7 @@ export async function syncPermissionRules(opts) {
|
|
|
190
197
|
const dotKeyOf = (d) => JSON.stringify([d.actor, d.counter]);
|
|
191
198
|
const preDots = new Set(current.rules.flatMap((r) => r.adds.map((a) => dotKeyOf(a.dot))));
|
|
192
199
|
const newAdds = landedRaw.rules.flatMap((r) => r.adds).filter((a) => !preDots.has(dotKeyOf(a.dot))).length;
|
|
193
|
-
const tombKeyOf = (t) => JSON.stringify([t.rule, t.scope.kind === "
|
|
200
|
+
const tombKeyOf = (t) => JSON.stringify([t.rule, t.scope.kind, t.scope.kind === "project" ? t.scope.root : t.scope.kind === "session" ? t.scope.sessionId : null, dotKeyOf(t.deletedBy)]);
|
|
194
201
|
const preTombs = new Set(current.tombstones.map(tombKeyOf));
|
|
195
202
|
const newTombstones = landedRaw.tombstones.filter((t) => !preTombs.has(tombKeyOf(t))).length;
|
|
196
203
|
const landedReport = res.sync;
|
|
@@ -228,13 +235,23 @@ function pickDot(v) {
|
|
|
228
235
|
const counter = d?.counter;
|
|
229
236
|
return typeof actor === "string" && actor !== "" && typeof counter === "number" && Number.isFinite(counter) ? { actor, counter } : undefined;
|
|
230
237
|
}
|
|
231
|
-
function
|
|
238
|
+
function pickScopeCaptured(v) {
|
|
232
239
|
const s = v;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
240
|
+
const kind = s?.kind;
|
|
241
|
+
if (kind === "global")
|
|
242
|
+
return { kind, scope: { kind: "global" } };
|
|
243
|
+
if (kind === "project") {
|
|
244
|
+
const root = s?.root;
|
|
245
|
+
return { kind, scope: typeof root === "string" && root !== "" ? { kind: "project", root } : undefined };
|
|
246
|
+
}
|
|
247
|
+
if (kind === "session") {
|
|
248
|
+
const sessionId = s?.sessionId;
|
|
249
|
+
return { kind, scope: typeof sessionId === "string" && sessionId !== "" ? { kind: "session", sessionId } : undefined };
|
|
250
|
+
}
|
|
251
|
+
return { kind, scope: undefined };
|
|
252
|
+
}
|
|
253
|
+
function pickScope(v) {
|
|
254
|
+
return pickScopeCaptured(v).scope;
|
|
238
255
|
}
|
|
239
256
|
function pickAdd(v) {
|
|
240
257
|
const a = v;
|
|
@@ -325,9 +342,11 @@ export function parseRuleSyncResponse(raw, expectedPrincipal) {
|
|
|
325
342
|
}
|
|
326
343
|
const tombstones = [];
|
|
327
344
|
const textRefusedTombstones = [];
|
|
345
|
+
const sessionScopeTombstones = [];
|
|
328
346
|
for (const entry of mergedTombstones) {
|
|
329
347
|
const t = entry;
|
|
330
|
-
const
|
|
348
|
+
const pickedScope = pickScopeCaptured(t?.scope);
|
|
349
|
+
const scope = pickedScope.scope;
|
|
331
350
|
const ruleText = t?.rule;
|
|
332
351
|
const deletedBy = pickDot(t?.deletedBy);
|
|
333
352
|
const removedRaw = t?.removedDots;
|
|
@@ -342,6 +361,15 @@ export function parseRuleSyncResponse(raw, expectedPrincipal) {
|
|
|
342
361
|
removedDots.push(dot);
|
|
343
362
|
}
|
|
344
363
|
}
|
|
364
|
+
if (pickedScope.kind === "session") {
|
|
365
|
+
if (typeof ruleText === "string" && deletedBy !== undefined && scope !== undefined) {
|
|
366
|
+
sessionScopeTombstones.push({ rule: ruleText, scope, dot: deletedBy });
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
warnings.push("the response carried a session-scope tombstone whose identity could not be read — dropped per-row (a session row never lands here, and nothing is withheld for it)");
|
|
370
|
+
}
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
345
373
|
if (typeof ruleText !== "string" || scope === undefined || deletedBy === undefined || removedDots.length === 0) {
|
|
346
374
|
tombstones.push(undefined);
|
|
347
375
|
continue;
|
|
@@ -384,6 +412,7 @@ export function parseRuleSyncResponse(raw, expectedPrincipal) {
|
|
|
384
412
|
...(gcFrontier !== undefined ? { gcFrontier } : {}),
|
|
385
413
|
dropped,
|
|
386
414
|
textRefusedTombstones,
|
|
415
|
+
sessionScopeTombstones,
|
|
387
416
|
warnings,
|
|
388
417
|
};
|
|
389
418
|
}
|
package/dist/core/roles.js
CHANGED
|
@@ -3,7 +3,7 @@ export function resolveModel(ref, models) {
|
|
|
3
3
|
if (typeof ref !== "string") {
|
|
4
4
|
return ref;
|
|
5
5
|
}
|
|
6
|
-
const m = models
|
|
6
|
+
const m = models !== undefined && Object.hasOwn(models, ref) ? models[ref] : undefined;
|
|
7
7
|
if (!m) {
|
|
8
8
|
throw new Error(`Unknown model ref "${ref}". Provide it in RunnerDeps.models or pass a Model object.`);
|
|
9
9
|
}
|
|
@@ -24,6 +24,7 @@ import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
|
24
24
|
import { askApproverIdentity, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOfLayer, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
|
|
25
25
|
const PERSISTED_RULE_TOOL = "Bash";
|
|
26
26
|
import { findAdmittingRule, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
27
|
+
import { normalizePersistedRule } from "../permission-rule-store.js";
|
|
27
28
|
import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
28
29
|
import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
|
|
29
30
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
@@ -893,6 +894,37 @@ function explicitlyDeferredMemoryTrio(mounted, roster, deferNames) {
|
|
|
893
894
|
function memoryGroupRetractionSet(builtinDeferPairNames, engineTrioInPlay) {
|
|
894
895
|
return new Set([...builtinDeferPairNames, ...(engineTrioInPlay ? MEMORY_ENGINE_TOOL_NAMES : [])]);
|
|
895
896
|
}
|
|
897
|
+
async function spliceSessionOverlayRows(overlay, sessionId, persisted, tracer, hostTaskId) {
|
|
898
|
+
if (overlay === undefined)
|
|
899
|
+
return persisted;
|
|
900
|
+
try {
|
|
901
|
+
const served = structuredClone(await overlay.read(sessionId));
|
|
902
|
+
const sessionRows = served.filter((r) => {
|
|
903
|
+
const scope = r?.scope;
|
|
904
|
+
return scope?.kind === "session" && scope.sessionId === sessionId && !("reject" in normalizePersistedRule(r));
|
|
905
|
+
});
|
|
906
|
+
if (sessionRows.length < served.length) {
|
|
907
|
+
emitTrace(tracer, () => ({
|
|
908
|
+
kind: "permission.rule_store_unreadable",
|
|
909
|
+
version: 1,
|
|
910
|
+
taskId: hostTaskId,
|
|
911
|
+
message: `session-rule overlay served ${served.length - sessionRows.length} row(s) that are not canonical session rows of this session — dropped, not adjudicated`,
|
|
912
|
+
ts: Date.now(),
|
|
913
|
+
}));
|
|
914
|
+
}
|
|
915
|
+
return sessionRows.length > 0 ? [...sessionRows, ...persisted] : persisted;
|
|
916
|
+
}
|
|
917
|
+
catch (err) {
|
|
918
|
+
emitTrace(tracer, () => ({
|
|
919
|
+
kind: "permission.rule_store_unreadable",
|
|
920
|
+
version: 1,
|
|
921
|
+
taskId: hostTaskId,
|
|
922
|
+
message: `session-rule overlay: ${err instanceof Error ? err.message : String(err)}`,
|
|
923
|
+
ts: Date.now(),
|
|
924
|
+
}));
|
|
925
|
+
return persisted;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
896
928
|
function refuseRequireExistingWithoutSession(spec) {
|
|
897
929
|
if (spec.requireExistingSession && !spec.sessionId) {
|
|
898
930
|
const e = new Error(`requireExistingSession requires a sessionId — cannot require an existing session without one (design/114 Phase3)`);
|
|
@@ -3600,10 +3632,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3600
3632
|
}));
|
|
3601
3633
|
return { unreadable: true };
|
|
3602
3634
|
}
|
|
3603
|
-
const
|
|
3635
|
+
const table = await spliceSessionOverlayRows(deps.sessionPermissionRules, sessionId, listed.rules, deps.tracer, hostTaskId);
|
|
3636
|
+
const admitting = findAdmittingRule(table, { tool: req.toolName, command, cwd: root, sessionId });
|
|
3604
3637
|
if (admitting !== undefined)
|
|
3605
3638
|
return persistedRuleHitOf(admitting);
|
|
3606
|
-
const coverage = segmentCoverageOf(command, { persisted:
|
|
3639
|
+
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: root, sessionId });
|
|
3607
3640
|
return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
|
|
3608
3641
|
},
|
|
3609
3642
|
};
|
|
@@ -1410,6 +1410,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1410
1410
|
...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
|
|
1411
1411
|
...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
|
|
1412
1412
|
...(subagentName ? { name: subagentName } : {}),
|
|
1413
|
+
model: prepared.model.id,
|
|
1413
1414
|
usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
|
|
1414
1415
|
status: "running",
|
|
1415
1416
|
...ident(),
|
|
@@ -4223,6 +4224,7 @@ export class Runner {
|
|
|
4223
4224
|
...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
|
|
4224
4225
|
...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
|
|
4225
4226
|
...(subagentName ? { name: subagentName } : {}),
|
|
4227
|
+
model: prepared.model.id,
|
|
4226
4228
|
usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
|
|
4227
4229
|
status: result.status === "completed" ? "completed" : "failed",
|
|
4228
4230
|
...ident(),
|
|
@@ -34,7 +34,11 @@ function liveDots(s) {
|
|
|
34
34
|
return out;
|
|
35
35
|
}
|
|
36
36
|
function sameScope(a, b) {
|
|
37
|
-
|
|
37
|
+
if (a.kind === "global")
|
|
38
|
+
return b.kind === "global";
|
|
39
|
+
if (a.kind === "session")
|
|
40
|
+
return b.kind === "session" && a.sessionId === b.sessionId;
|
|
41
|
+
return b.kind === "project" && a.root === b.root;
|
|
38
42
|
}
|
|
39
43
|
function allDots(s) {
|
|
40
44
|
return new Set(s.rules.flatMap((r) => r.adds.map((a) => `${r.rule}|${a.dot.actor}#${a.dot.counter}`)));
|
|
@@ -150,6 +154,16 @@ export async function permissionRuleSyncContract(hooks = {}) {
|
|
|
150
154
|
const merged = join(state([rule("Bash(node:*)", [add("z", 1)])]), state());
|
|
151
155
|
assert.strictEqual(allDots(merged).size, 0, "Bash(node:*) must be refused by the shared validator inside the join");
|
|
152
156
|
});
|
|
157
|
+
run("durable two-member face (design/382 §4.3): a session-scope row or tombstone never survives a join, and never poisons the round", async () => {
|
|
158
|
+
const sessionScope = { kind: "session", sessionId: "sess-1" };
|
|
159
|
+
const dirty = state([rule("Bash(ls)", [add("z", 1)], sessionScope), rule("Bash(git status)", [add("a", 1)])], [tomb("Bash(pwd)", [["z", 2]], ["z", 3], sessionScope)]);
|
|
160
|
+
const ab = join(dirty, state());
|
|
161
|
+
const ba = join(state(), dirty);
|
|
162
|
+
assert.ok(!allDots(ab).has("Bash(ls)|z#1"), "a session-scope add must not survive the join — the durable face holds two members");
|
|
163
|
+
assert.strictEqual(ab.tombstones.length, 0, "a session-scope tombstone must not survive the join");
|
|
164
|
+
assert.ok(allDots(ab).has("Bash(git status)|a#1"), "dropping the session row must not withhold or disturb the durable rest — per-row, never round-poisoning");
|
|
165
|
+
assert.deepStrictEqual(ab, ba, "the session-scope drop must preserve commutativity");
|
|
166
|
+
});
|
|
153
167
|
run("identity keys are collision-free: roots and actors containing spaces keep DISTINCT tombstone identities, and the covered add stays dead through an identity join", async () => {
|
|
154
168
|
const scopeA = { kind: "project", root: "/x" };
|
|
155
169
|
const scopeB = { kind: "project", root: "/x a" };
|
|
@@ -66,6 +66,19 @@ export interface TaskNotificationPayload {
|
|
|
66
66
|
* `TaskResult.errorCode` taxonomy (brain `[code]` prefixes via extractErrorCode, `limit.*`,
|
|
67
67
|
* `budget.*`, …). Engine-minted, never model text. Absent when the failure carried no code. */
|
|
68
68
|
errorCode?: string;
|
|
69
|
+
/** Terminal, delegation lanes: the settling run's PROVIDER-BOUNDARY fault assertion, mirrored from
|
|
70
|
+
* its `TaskResult.apiFailure`. Its PRESENCE is the claim — "this failure came from the transport or
|
|
71
|
+
* the provider, not from this deployment refusing to send, a limit, or unusable model output" — and
|
|
72
|
+
* `errorCode` beside it names WHICH terminal; the two answer different questions and neither implies
|
|
73
|
+
* the other. Members are whatever the failing attempt stated about itself (`status` when the provider
|
|
74
|
+
* answered with one, `requestId` when it named one), so an EMPTY object is meaningful: "provider
|
|
75
|
+
* fault, unlabelled", which is not the same statement as absence. Absent on every non-provider
|
|
76
|
+
* terminal, on every completed run, and on the non-delegation lanes (bash/monitor), whose failures do
|
|
77
|
+
* not pass through a model provider at all. */
|
|
78
|
+
apiFailure?: {
|
|
79
|
+
status?: number;
|
|
80
|
+
requestId?: string;
|
|
81
|
+
};
|
|
69
82
|
/** Structured exit code of a background command's terminal notification (bash lane): the process
|
|
70
83
|
* exited on its own with this code. Lets consumers branch on success/failure without parsing the
|
|
71
84
|
* summary wording. Absent when the process never exited by itself (killed / spawn-failed lanes). */
|
|
@@ -77,6 +90,13 @@ export interface TaskNotificationPayload {
|
|
|
77
90
|
* every other notification (a completed result is never flagged). */
|
|
78
91
|
partial?: boolean;
|
|
79
92
|
output_file?: string;
|
|
93
|
+
/** Whatever the settling lane knows about its own spend, JSON-rendered verbatim into one `<usage>`
|
|
94
|
+
* tag. Deliberately untyped — the lanes report different quantities and nothing here should force
|
|
95
|
+
* one to fabricate a figure it does not have. What IS contracted is the VOCABULARY: a name that two
|
|
96
|
+
* lanes both publish must mean the same thing on both. The delegation lanes
|
|
97
|
+
* (`background_agent`, its fork/revive cycles) and the workflow lane therefore agree on
|
|
98
|
+
* `tokens`/`turns`/`costMicroUsd`/`tool_uses`/`duration_ms`; the workflow lane's fan-out counters
|
|
99
|
+
* (`agent_count`, `agents_done`, …) and resume counters have no delegation analog and are its own. */
|
|
80
100
|
usage?: unknown;
|
|
81
101
|
/** CC `<diagnostics>` parity: ENGINE-MINTED teaching text for the "result is empty/unexpected —
|
|
82
102
|
* now what" moment (per-agent read route / journal coordinate / resume command). Producers must mint it
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -449,7 +449,10 @@ export type TraceEvent = {
|
|
|
449
449
|
/** The bucket this round synced. A local-owner bucket cannot sync, so this is always a principal. */
|
|
450
450
|
principal: string;
|
|
451
451
|
rule: string;
|
|
452
|
-
|
|
452
|
+
/** `"session"` is unreachable here in a healthy fleet (design/382 §4.3: a session row never
|
|
453
|
+
* lands, so it can never resurrect) — declared because the sibling drop event names it and a
|
|
454
|
+
* lockstep union is what keeps the two from drifting. */
|
|
455
|
+
scopeKind: "global" | "project" | "session";
|
|
453
456
|
ts: number;
|
|
454
457
|
} | {
|
|
455
458
|
/**
|
|
@@ -462,7 +465,9 @@ export type TraceEvent = {
|
|
|
462
465
|
version: 1;
|
|
463
466
|
principal: string;
|
|
464
467
|
rule: string;
|
|
465
|
-
|
|
468
|
+
/** `"session"` names the design/382 §4.3 durable-face drop: a peer sent a session-scope row,
|
|
469
|
+
* refused per-row with `session_scope_not_durable`. */
|
|
470
|
+
scopeKind: "global" | "project" | "session";
|
|
466
471
|
/** Closed reason code ({@link import("./governance-codes.js").RuleSyncDropReason}). */
|
|
467
472
|
reason: string;
|
|
468
473
|
ts: number;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -4646,6 +4646,31 @@ export type TaskEvent = ({
|
|
|
4646
4646
|
* nor an agent-type — the child then keeps its taskId (NOT the raw objective, which could leak a delegated
|
|
4647
4647
|
* secret to a progress-scoped consumer — dual-review Q2). Sanitized (control-char-stripped, length-capped). */
|
|
4648
4648
|
name?: string;
|
|
4649
|
+
/**
|
|
4650
|
+
* The model this sub-run was PREPARED with — the resolved id, read off the leg's prepared model
|
|
4651
|
+
* at the mint, so it is the same value `TaskResult.model` and the `task.start` trace frame carry.
|
|
4652
|
+
* (`task.end` reports the run's totals and has never named a model; there is nothing to join to
|
|
4653
|
+
* there.)
|
|
4654
|
+
*
|
|
4655
|
+
* Why it rides the live lane at all: a delegation may name a tier word (`"sonnet"`), an agent
|
|
4656
|
+
* definition's model, or NOTHING (inherit the caller's current model, or fall to the `subagent`
|
|
4657
|
+
* role) — every one of those resolves somewhere the consumer cannot see, so a shell badging a
|
|
4658
|
+
* running child previously had only the REQUESTED word (or nothing) to render, which is a
|
|
4659
|
+
* different claim from what the child runs on. This is the resolved answer, not the request.
|
|
4660
|
+
*
|
|
4661
|
+
* PREPARED, deliberately, and NOT "whatever is serving this turn" — stated in the first sentence
|
|
4662
|
+
* because the difference is observable. A mid-run DEGRADE switch (`spec.limits.degrade`) or a
|
|
4663
|
+
* gateway re-route moves the serving model without rewriting this field, exactly as
|
|
4664
|
+
* `TaskResult.model` behaves; the switch is announced on its own channel (`TaskResult.degraded`),
|
|
4665
|
+
* and the per-call served id lives on the brain-call telemetry. One name, one meaning, across the
|
|
4666
|
+
* three faces that use it — at the cost of being the leg's declared model rather than a live one.
|
|
4667
|
+
*
|
|
4668
|
+
* Present on every tick this build mints (the frame family is subagent-only by construction, so
|
|
4669
|
+
* there is no lane where a leg has no prepared model). Declared optional for the ordinary reason:
|
|
4670
|
+
* a consumer folding frames from a pre-key producer must keep compiling, and absence there means
|
|
4671
|
+
* "this producer did not state it", never "no model".
|
|
4672
|
+
*/
|
|
4673
|
+
model?: string;
|
|
4649
4674
|
/** The child's most recent tool intent as one human line ("Bash npm test",
|
|
4650
4675
|
* "Edit src/x.ts") — the SAME source/value as the registry sink tick's `currentAction` (residual
|
|
4651
4676
|
* observability, lane B), attached to the FORWARDED frame because every client wire projects this
|
|
@@ -5246,6 +5271,40 @@ export interface BackgroundChildEvent {
|
|
|
5246
5271
|
* waiting for spawn-frame forwarding; `name` stays the design/99 DISPLAY label (description-backed)
|
|
5247
5272
|
* and was never a type field. */
|
|
5248
5273
|
agentType?: string;
|
|
5274
|
+
/**
|
|
5275
|
+
* spawn + tick: the model the ROW runs on — the resolved `ModelRef` the spawner selected for this
|
|
5276
|
+
* child (a per-call `model`, an agent definition's, or the caller's own current model, inherited).
|
|
5277
|
+
* It answers with the SERVED catalog model id wherever that is knowable, because this value sits
|
|
5278
|
+
* beside the child's own `task_progress.model` (the leg's prepared id) and a consumer joins the
|
|
5279
|
+
* two: a row saying "sonnet" next to ticks naming the catalog id it routed to is indistinguishable
|
|
5280
|
+
* from two different children. A `Model` object carries its id — and a per-call word that passed
|
|
5281
|
+
* the spawn gate arrives as exactly that resolved object, so for it the row and the ticks are one
|
|
5282
|
+
* value by construction. A definition/tool-level STRING ref resolves through the catalog in force
|
|
5283
|
+
* at the spawn judgement; the child's own prepare re-resolves such a ref against the runner's live
|
|
5284
|
+
* table, so for those refs alone a catalog hot-swapped between the two reads can lag the row one
|
|
5285
|
+
* generation behind the ticks. The DISPLAY rule survives only as the FALLBACK, for a string the
|
|
5286
|
+
* judgement-time catalog cannot resolve (a CC tier alias then shows its sema tier, never the alias
|
|
5287
|
+
* verbatim). It rides ticks as well as spawn for the same reason
|
|
5288
|
+
* {@link agentType} does: a consumer that only forwards ticks must not have to wait for spawn-frame
|
|
5289
|
+
* forwarding to fill its row. Like `agentType` it is the ROW's own fact and is NEVER copied off a
|
|
5290
|
+
* forwarded frame — a nested descendant's `task_progress` names ITS model, not this row's.
|
|
5291
|
+
*
|
|
5292
|
+
* ABSENT is a fact, not a gap, and there are exactly three ways to get there:
|
|
5293
|
+
* · the delegation named no model anywhere in the chain, so the child runs on the `subagent` ROLE
|
|
5294
|
+
* and its concrete id is only decided at the child's own prepare;
|
|
5295
|
+
* · the RETAIN-LEDGER wake lane (a `SendMessage` resume of a completed child), whose frames are
|
|
5296
|
+
* projected from an `AccessibleTaskRow` — and that row has no model column, so this lane has
|
|
5297
|
+
* nothing to state and will not invent one. Stated precisely because the sibling wake lane does
|
|
5298
|
+
* NOT share the limitation: a tier-3 DURABLE revive re-enters through the ordinary background
|
|
5299
|
+
* spawn, re-derives the model like a first spawn, and its `(revived)` frames carry it whenever
|
|
5300
|
+
* that derivation lands on a model (the durable record keeps a `model` of its own; a recorded
|
|
5301
|
+
* key the current catalog no longer resolves degrades the revival to the inherited model, and a
|
|
5302
|
+
* mount with nothing to inherit then leaves these frames honestly silent);
|
|
5303
|
+
* · a pre-key producer.
|
|
5304
|
+
* In every case the child's own `task_progress` frames still carry the resolved answer, which is the
|
|
5305
|
+
* authority; this field is the row-level convenience beside it.
|
|
5306
|
+
*/
|
|
5307
|
+
model?: string;
|
|
5249
5308
|
/** spawn: the HOST task's DECLARED task id (parent attribution). Omitted when the host
|
|
5250
5309
|
* run declared no task id (the `spec.taskId ?? sessionId` fallback would launder a session id into
|
|
5251
5310
|
* a task-id field — the orphan-pointer shape); {@link parentSessionId} is the always-on linkage. */
|
|
@@ -5359,7 +5418,21 @@ export interface BackgroundChildEvent {
|
|
|
5359
5418
|
stoppedBy?: "user" | "parent" | "system" | (string & {});
|
|
5360
5419
|
/** terminal: bounded human summary (same text the task_notification carries). */
|
|
5361
5420
|
summary?: string;
|
|
5362
|
-
/** tick: live rollup (`task_progress.usage`) · terminal:
|
|
5421
|
+
/** tick: live rollup (`task_progress.usage`) · terminal: the settled
|
|
5422
|
+
* `{tokens, turns, costMicroUsd?, toolUses?, durationMs}`. `costMicroUsd` is own+nested and present
|
|
5423
|
+
* only when that total is KNOWN (RB-368); `toolUses` is the run's own `stats.toolCalls` and is
|
|
5424
|
+
* absent — never zeroed — when the gateway reported no usage. The last two joined the terminal face
|
|
5425
|
+
* to close a reporting asymmetry: the workflow lane's completion notification and the Agent tool's
|
|
5426
|
+
* own sync `<usage>` footer had published both all along, so one child answered "how much work, how
|
|
5427
|
+
* long" on one lane and refused on the other.
|
|
5428
|
+
*
|
|
5429
|
+
* `durationMs` MEASURES DIFFERENT SPANS on the two kinds, which is worth knowing before plotting it:
|
|
5430
|
+
* on a TICK it is the child RUN's own elapsed (the forwarded frame's value, from the run's start);
|
|
5431
|
+
* on the TERMINAL it is the delegation LEG's, from the lane's launch instant — which is earlier, so
|
|
5432
|
+
* a consumer graphing one series sees a step up at settle. Both are true of what they name; neither
|
|
5433
|
+
* can be computed from the other without the spawn/prepare interval, which is why they are not
|
|
5434
|
+
* reconciled into one. The intra-turn activity beat carries a third, narrower shape (`{toolUses}`
|
|
5435
|
+
* alone — there is no honest live token figure on that lane). */
|
|
5363
5436
|
usage?: {
|
|
5364
5437
|
totalTokens?: number;
|
|
5365
5438
|
toolUses?: number;
|
|
@@ -6436,6 +6509,17 @@ export interface RunnerDeps {
|
|
|
6436
6509
|
* adopted principal's bucket forever.
|
|
6437
6510
|
*/
|
|
6438
6511
|
localOwnerRules?: boolean;
|
|
6512
|
+
/**
|
|
6513
|
+
* design/382 §4.3 — the SESSION-RULE OVERLAY: where `{kind:"session"}` scoped allow rules live
|
|
6514
|
+
* (the session's own state, never the persisted store). Wired, the gate's persisted-rule lane
|
|
6515
|
+
* splices the CURRENT session's overlay rows in front of the store's `list()` on every
|
|
6516
|
+
* adjudication — session > project > global, the narrowest-first reporting order — and threads the
|
|
6517
|
+
* session's identity as the eligibility context's third axis; a host lands rows into it through
|
|
6518
|
+
* the consent protocol (`RuleConsentDeps.sessionRules`, a session-scope candidate's redemption).
|
|
6519
|
+
* Omitted ⇒ the session dimension does not exist at this gate and the lane is byte-identical.
|
|
6520
|
+
* See `permission-rule-session.ts` for the contract and the reference implementation.
|
|
6521
|
+
*/
|
|
6522
|
+
sessionPermissionRules?: import("./permission-rule-session.js").SessionRuleOverlay;
|
|
6439
6523
|
/**
|
|
6440
6524
|
* design/182 §7 — the ORG rule overlay for an org-GOVERNED deployment. Constructed with
|
|
6441
6525
|
* `createOrgRuleOverlay` (that constructor is the boot gate: a governed declaration with no snapshot
|
|
@@ -133,7 +133,12 @@ export interface WiringManifest {
|
|
|
133
133
|
* a completed/failed/killed(non-user) subagent minted through the deps-visible assembly is
|
|
134
134
|
* continuable across a process restart. A statement about the DEPS-VISIBLE assembly only: a
|
|
135
135
|
* caller-mounted Agent tool over its own runner is outside this manifest's sight (per-read
|
|
136
|
-
* honest degrade + the integrity notice own that case, never a fabricated tier)
|
|
136
|
+
* honest degrade + the integrity notice own that case, never a fabricated tier) — and that
|
|
137
|
+
* blind spot is load-bearing here, because the store this field is derived from is the ROOT
|
|
138
|
+
* leg's while the store an ordinary child transcript is actually minted through is the AGENT
|
|
139
|
+
* TOOL's runner's ({@link resolveSubagentTranscriptTier} names the rule and the fork-lane
|
|
140
|
+
* exception). The word names ADDRESSABILITY: `none`/`rows` are silent on whether a child
|
|
141
|
+
* transcript reaches disk at all, and only `full` promises the a* handle survives a restart. */
|
|
137
142
|
fleet: {
|
|
138
143
|
backgroundAgentStore: boolean;
|
|
139
144
|
hostChildEventSink: boolean;
|
|
@@ -254,6 +259,18 @@ export type SubagentTranscriptTier = "none" | "rows" | "full";
|
|
|
254
259
|
* fold picked). This is the DEPLOYMENT-level judgment only; the per-handle "did this row's
|
|
255
260
|
* transcript actually land" question belongs to `durableAgentRowProbe` (the S1b release-flip gate)
|
|
256
261
|
* — two different questions, deliberately two named faces (do not merge them back into one).
|
|
262
|
+
*
|
|
263
|
+
* WHICH session store to pass: the one on the runner the Agent tool was mounted with
|
|
264
|
+
* (`SubagentToolOptions.runner.sessions`), because that is the store an ordinary (sync/background)
|
|
265
|
+
* child transcript is minted through — the fork lane deliberately branches through the HOST store
|
|
266
|
+
* instead, see the law on `SubagentToolOptions.runner`. A deployment that serves root tasks from one
|
|
267
|
+
* runner and delegated children from another (a throwaway sub-task store, a routing store) has two
|
|
268
|
+
* different stores here, and passing the ROOT deps' store reports the root lane's fate under the
|
|
269
|
+
* child lane's name. Note what the three words do and do not promise about DISK: this tier names
|
|
270
|
+
* ADDRESSABILITY, not persistence. `none` is returned before any store is inspected, so it covers
|
|
271
|
+
* both "the transcript is on disk but nothing can address it" and "there is no transcript" — an
|
|
272
|
+
* operator asking whether a child conversation is readable afterwards must read the delegation
|
|
273
|
+
* store's own durability + release/evict semantics, not this word.
|
|
257
274
|
*/
|
|
258
275
|
export declare function resolveSubagentTranscriptTier(agentStoreWired: boolean, sessionStore: {
|
|
259
276
|
readonly placements?: {
|
package/dist/index.d.ts
CHANGED
|
@@ -167,8 +167,9 @@ export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRul
|
|
|
167
167
|
* allowed and widening is not.
|
|
168
168
|
*/
|
|
169
169
|
export { parseAllowRuleText, formatAllowRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, type PersistedAllowRule, type RuleTombstone, type RuleScope, type RuleDot, type RuleAdd, type RuleAddOrigin, type RuleOffer, type SegmentRuleSuggestion, type SegmentCoverage, type RuleReject, type RuleRejectCode, type ParsedAllowRule, type PersistedRuleTool, type PersistedRuleMatch, } from "./core/permission-rule-model.js";
|
|
170
|
-
export { removePersistedRule, applyTombstones, sameScope, InMemoryPermissionRuleStore, EMPTY_RULE_STORE, type PermissionRuleStore, type PermissionRuleStoreProvider, type StoredAllowRules, type RemoveResult, type PutResult, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, type RuleSyncState, type RuleSyncFrontier, type RuleSyncDrop, type RuleSyncLandingReport, type RuleOwner, type QuarantinedRuleAdd, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, type PermissionRuleWriter, type WritablePermissionRuleStore, type RuleWriteDelta, type RuleAddDelta, type RuleDeleteDelta, type RuleSyncJoinDelta, type RawRuleSyncState, type RedemptionAuthorization, } from "./core/permission-rule-store.js";
|
|
170
|
+
export { removePersistedRule, applyTombstones, sameScope, isValidConsentScope, isValidDurableScope, InMemoryPermissionRuleStore, EMPTY_RULE_STORE, type PermissionRuleStore, type PermissionRuleStoreProvider, type StoredAllowRules, type RemoveResult, type PutResult, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, type RuleSyncState, type RuleSyncFrontier, type RuleSyncDrop, type RuleSyncLandingReport, type RuleOwner, type QuarantinedRuleAdd, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, type PermissionRuleWriter, type WritablePermissionRuleStore, type RuleWriteDelta, type RuleAddDelta, type RuleDeleteDelta, type RuleSyncJoinDelta, type RawRuleSyncState, type RedemptionAuthorization, } from "./core/permission-rule-store.js";
|
|
171
171
|
export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, type PermissionRuleSyncTransport, type PermissionRuleSyncResult, type RuleSyncRequestBody, type RuleSyncResponseBody, } from "./core/permission-rule-sync.js";
|
|
172
|
+
export { InMemorySessionRuleOverlay, type SessionRuleOverlay, type SessionRuleOverlayAdd, type SessionRuleOverlayApplyResult, } from "./core/permission-rule-session.js";
|
|
172
173
|
export { createOrgRuleOverlay, orgRuleVerdictFor, effectivePermissionRules, orgRuleStatePersistenceOf, ORG_UNAVAILABLE_DECISION_REASON, ORG_RULE_DECISION_REASON, ORG_ADJUDICATION_TIMEOUT_MS, type OrgPermissionRule, type OrgRuleSnapshot, type OrgRuleSnapshotProvider, type OrgRuleStatePersistence, type PersistedOrgRuleState, type OrgRuleOverlay, type OrgOverlayResolution, type OrgOverlayStatus, type EffectivePermissionRule, } from "./core/permission-rule-org.js";
|
|
173
174
|
export { RULE_SYNC_DROP_CODES, type RuleSyncDropReason, type RuleQuarantineReason } from "./core/governance-codes.js";
|
|
174
175
|
export { prepareCardApproval, confirmRuleApproval, type ConfirmResult, type ConfirmRefusalReason, precheckEditedRuleText, type EditedRuleTextPrecheck, redeemRuleTicket, redeemRuleBatch, prepareCcImport, prepareStarterBatch, mintRuleTicket, STARTER_RULES, InMemoryRuleApprovalRecordStore, ruleOffersOfRecord, type RuleTicket, type RuleCandidate, type RuleApprovalKind, type RuleApprovalRecord, type RuleApprovalRecordStore, type RuleOffer2, type StaleRuleApprovalRecord, type RuleConsentDeps, type RedeemResult, type RedeemedBatchMember, type CcImportLayer, type ImportedSettingsLayer, type ImportPreview, } from "./core/permission-rule-consent.js";
|
package/dist/index.js
CHANGED
|
@@ -127,8 +127,9 @@ export { AUTO_MODE_ARMING_RECIPE_VERSION, autoModeArmingRecipeOf, sanitizeAutoMo
|
|
|
127
127
|
export { rebuildAutoModeDecider, } from "./core/auto-mode-rebuild.js";
|
|
128
128
|
export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, isNamespacedCoveringRuleName, namespacedRuleNameCovers, } from "./core/permission-rules.js";
|
|
129
129
|
export { parseAllowRuleText, formatAllowRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, } from "./core/permission-rule-model.js";
|
|
130
|
-
export { removePersistedRule, applyTombstones, sameScope, InMemoryPermissionRuleStore, EMPTY_RULE_STORE, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, } from "./core/permission-rule-store.js";
|
|
130
|
+
export { removePersistedRule, applyTombstones, sameScope, isValidConsentScope, isValidDurableScope, InMemoryPermissionRuleStore, EMPTY_RULE_STORE, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, } from "./core/permission-rule-store.js";
|
|
131
131
|
export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, } from "./core/permission-rule-sync.js";
|
|
132
|
+
export { InMemorySessionRuleOverlay, } from "./core/permission-rule-session.js";
|
|
132
133
|
export { createOrgRuleOverlay, orgRuleVerdictFor, effectivePermissionRules, orgRuleStatePersistenceOf, ORG_UNAVAILABLE_DECISION_REASON, ORG_RULE_DECISION_REASON, ORG_ADJUDICATION_TIMEOUT_MS, } from "./core/permission-rule-org.js";
|
|
133
134
|
export { RULE_SYNC_DROP_CODES } from "./core/governance-codes.js";
|
|
134
135
|
export { prepareCardApproval, confirmRuleApproval, precheckEditedRuleText, redeemRuleTicket, redeemRuleBatch, prepareCcImport, prepareStarterBatch, mintRuleTicket, STARTER_RULES, InMemoryRuleApprovalRecordStore, ruleOffersOfRecord, } from "./core/permission-rule-consent.js";
|
|
@@ -3,6 +3,7 @@ import { defineTool, errorResult } from "../core/tools.js";
|
|
|
3
3
|
import { governanceBaselineError, governanceBaselineProblem } from "./governance-baseline-validity.js";
|
|
4
4
|
import { redactSecrets, redactHostLeaks, boundedRedactedSummary } from "../core/untrusted-egress.js";
|
|
5
5
|
import { withDelegationProvenance } from "../core/tool-policy.js";
|
|
6
|
+
import { LAUNCH_RECEIPT_OWN_WORDS_CLAUSE, launchReceiptNoQuoteClause } from "../agents/launch-receipt-contract.js";
|
|
6
7
|
import { startWorkflow } from "./workflow.js";
|
|
7
8
|
import { buildWorkflowPrimitives } from "./workflow-primitives.js";
|
|
8
9
|
import { parseWorkflowMeta, splitWorkflowMeta, workflowScriptReadsClockOrRandom } from "./workflow-meta.js";
|
|
@@ -630,8 +631,7 @@ export async function createRunWorkflowTool(d) {
|
|
|
630
631
|
task_id: runId,
|
|
631
632
|
status: "started",
|
|
632
633
|
...(persistedScriptPath !== undefined ? { scriptPath: persistedScriptPath } : {}),
|
|
633
|
-
handling: "
|
|
634
|
-
"In your own words, briefly tell the user what you launched; do not echo this result.",
|
|
634
|
+
handling: `${launchReceiptNoQuoteClause(" (the ids above, and scriptPath when present)")} ${LAUNCH_RECEIPT_OWN_WORDS_CLAUSE}`,
|
|
635
635
|
note: (() => {
|
|
636
636
|
const pollExpr = d.taskRegistry ? `TaskOutput({ task_id: "${runId}" })` : undefined;
|
|
637
637
|
const blockingPollExpr = d.taskRegistry ? `TaskOutput({ task_id: "${runId}", block: true })` : undefined;
|