@sema-agent/core 5.65.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/dist/agents/subagent.d.ts +2 -2
- package/dist/agents/subagent.js +11 -0
- package/dist/agents/verify.d.ts +1 -1
- package/dist/brain/anthropic.js +1 -1
- package/dist/brain/errors.d.ts +29 -0
- package/dist/brain/errors.js +20 -0
- package/dist/brain/open-responses.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +1 -0
- package/dist/brain/status-sink.js +12 -1
- package/dist/brain/stream-engine.js +17 -6
- package/dist/core/auto-compaction.d.ts +26 -0
- package/dist/core/auto-compaction.js +7 -2
- package/dist/core/auto-mode-arming.d.ts +138 -0
- package/dist/core/auto-mode-arming.js +181 -0
- package/dist/core/auto-mode-defaults.d.ts +13 -0
- package/dist/core/auto-mode-defaults.js +5 -0
- package/dist/core/auto-mode-prompt.d.ts +14 -3
- package/dist/core/auto-mode-prompt.js +10 -7
- package/dist/core/auto-mode-rebuild.d.ts +75 -0
- package/dist/core/auto-mode-rebuild.js +41 -0
- package/dist/core/auto-mode.d.ts +15 -0
- package/dist/core/auto-mode.js +4 -2
- package/dist/core/checkpoint-store.d.ts +18 -0
- package/dist/core/context-edit.d.ts +47 -5
- package/dist/core/context-guard.d.ts +1 -1
- package/dist/core/file-history-retention.d.ts +106 -0
- package/dist/core/file-history-retention.js +36 -0
- package/dist/core/file-history-store.d.ts +768 -0
- package/dist/core/file-history-store.js +880 -0
- package/dist/core/governance-codes.d.ts +13 -3
- package/dist/core/governance-codes.js +15 -0
- package/dist/core/hooks.d.ts +39 -7
- package/dist/core/hooks.js +38 -21
- package/dist/core/lsp.d.ts +2 -2
- package/dist/core/mcp.d.ts +29 -7
- package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
- package/dist/core/memory-engine/consolidation-driver.js +71 -4
- package/dist/core/memory-engine/consolidation.d.ts +25 -2
- package/dist/core/memory-engine/consolidation.js +4 -1
- package/dist/core/memory-engine/distiller.d.ts +84 -1
- package/dist/core/memory-engine/distiller.js +68 -0
- package/dist/core/memory-engine/dual-root.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +328 -15
- package/dist/core/memory-engine/engine.js +355 -29
- package/dist/core/memory-engine/file-backend.d.ts +30 -0
- package/dist/core/memory-engine/file-backend.js +14 -13
- package/dist/core/memory-engine/frontmatter.d.ts +22 -1
- package/dist/core/memory-engine/frontmatter.js +3 -0
- package/dist/core/memory-engine/header-hints.d.ts +5 -0
- package/dist/core/memory-engine/index.d.ts +5 -4
- package/dist/core/memory-engine/index.js +5 -4
- package/dist/core/memory-engine/layout.d.ts +88 -2
- package/dist/core/memory-engine/layout.js +112 -3
- package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
- package/dist/core/memory-engine/provenance-wording.js +3 -0
- package/dist/core/memory-engine/tools.d.ts +89 -8
- package/dist/core/memory-engine/tools.js +263 -22
- package/dist/core/memory-engine/types.d.ts +64 -1
- package/dist/core/memory-recall.d.ts +6 -0
- package/dist/core/memory.d.ts +27 -1
- package/dist/core/memory.js +16 -2
- package/dist/core/permission-rule-consent.d.ts +62 -2
- package/dist/core/permission-rule-consent.js +105 -14
- package/dist/core/permission-rule-model.d.ts +118 -16
- package/dist/core/permission-rule-model.js +57 -9
- 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 +75 -16
- package/dist/core/permission-rule-sync.d.ts +9 -0
- package/dist/core/permission-rule-sync.js +52 -19
- package/dist/core/retention-policy.d.ts +9 -0
- package/dist/core/retention-policy.js +5 -2
- package/dist/core/retention.d.ts +13 -2
- package/dist/core/runner/assemble-result.d.ts +19 -1
- package/dist/core/runner/assemble-result.js +17 -2
- package/dist/core/runner/compaction-call-options.d.ts +93 -0
- package/dist/core/runner/compaction-call-options.js +3 -0
- package/dist/core/runner/memory-capture-optout.d.ts +80 -0
- package/dist/core/runner/memory-capture-optout.js +53 -0
- package/dist/core/runner/prepare-config-doors.d.ts +5 -0
- package/dist/core/runner/prepare-config-doors.js +16 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +110 -5
- package/dist/core/runner/prepare-hands-readface.js +99 -7
- package/dist/core/runner/prepare-memory.d.ts +88 -0
- package/dist/core/runner/prepare-memory.js +305 -24
- package/dist/core/runner/prepare-task.d.ts +141 -1
- package/dist/core/runner/prepare-task.js +478 -81
- package/dist/core/runner/runtask.d.ts +9 -20
- package/dist/core/runner/runtask.js +133 -96
- package/dist/core/runner/session-file-state-replay.d.ts +18 -10
- package/dist/core/runner/session-file-state-replay.js +52 -1
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/runner/turn-attachments.d.ts +22 -12
- package/dist/core/session-store.d.ts +1 -1
- package/dist/core/session-store.js +6 -1
- package/dist/core/session.d.ts +34 -1
- package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/file-history-store-contract.js +720 -0
- package/dist/core/store-contracts/permission-rule-sync-contract.js +15 -1
- package/dist/core/task-registry-shared.js +11 -1
- package/dist/core/tool-errors.js +1 -0
- package/dist/core/tool-policy.d.ts +172 -1
- package/dist/core/tool-policy.js +32 -1
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/trace.d.ts +31 -2
- package/dist/core/types.d.ts +795 -89
- package/dist/core/types.js +4 -3
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/workflow-run-store-contract.js +6 -2
- package/dist/core/workflow-run-store.d.ts +4 -1
- package/dist/engine/compaction/compaction.d.ts +88 -10
- package/dist/engine/compaction/compaction.js +109 -30
- package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
- package/dist/engine/execution-env/node-execution-env.js +28 -0
- package/dist/engine/harness/agent-harness.d.ts +52 -1
- package/dist/engine/harness/agent-harness.js +36 -1
- package/dist/engine/harness/types.d.ts +26 -1
- package/dist/engine/llm/types.d.ts +50 -4
- package/dist/engine/loop/agent-loop.d.ts +5 -1
- package/dist/engine/loop/agent-loop.js +25 -0
- package/dist/engine/loop/types.d.ts +19 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
- package/dist/engine/session/session.js +1 -1
- package/dist/index.d.ts +20 -9
- package/dist/index.js +16 -7
- package/dist/orchestration/run-workflow-tool.d.ts +20 -2
- package/dist/orchestration/run-workflow-tool.js +22 -3
- package/dist/orchestration/workflow-governance.d.ts +59 -1
- package/dist/orchestration/workflow-governance.js +61 -8
- package/dist/orchestration/workflow-meta.d.ts +4 -2
- package/dist/orchestration/workflow-primitives.js +56 -13
- package/dist/orchestration/workflow-types.d.ts +78 -2
- package/dist/orchestration/workflow.d.ts +20 -0
- package/dist/orchestration/workflow.js +163 -14
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/dist/prompts/default.d.ts +7 -7
- package/dist/stores/file/file-history-store.d.ts +368 -0
- package/dist/stores/file/file-history-store.js +1248 -0
- package/dist/stores/file/index.d.ts +22 -13
- package/dist/stores/file/index.js +4 -4
- package/dist/stores/file/permission-rule-store.d.ts +11 -0
- package/dist/stores/file/permission-rule-store.js +23 -9
- package/dist/stores/file/strategy-store.d.ts +3 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
- package/dist/tools/fs/bash-readonly-classifier.js +106 -4
- package/dist/tools/fs/fs-bash.js +9 -5
- package/dist/tools/fs/fs-shared.d.ts +52 -1
- package/dist/tools/fs/fs-shared.js +14 -0
- package/dist/tools/fs/fs-write.d.ts +5 -5
- package/dist/tools/fs/fs-write.js +71 -14
- package/dist/tools/fs/index.d.ts +6 -1
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/web.js +2 -1
- package/package.json +5 -1
- package/test/export-surface.snapshot.json +169 -23
- package/dist/core/file-snapshot-store.d.ts +0 -165
- package/dist/core/file-snapshot-store.js +0 -259
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
- package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
- package/dist/stores/file/file-snapshot-store.d.ts +0 -58
- package/dist/stores/file/file-snapshot-store.js +0 -353
|
@@ -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" };
|
|
@@ -180,7 +180,17 @@ function timeoutInterruptionNote(interruption) {
|
|
|
180
180
|
return "";
|
|
181
181
|
return (` INTERRUPTED by the workflow's total timeout (${interruption.timeoutMs.toLocaleString()}ms): when the deadline fired ` +
|
|
182
182
|
`${interruption.agentsCompleted} agent(s) had completed, ${interruption.agentsFailed} had failed, and ` +
|
|
183
|
-
`${interruption.agentsInFlight} were still in flight — the failed run status is the deadline's, not a verdict on those agents.`
|
|
183
|
+
`${interruption.agentsInFlight} were still in flight — the failed run status is the deadline's, not a verdict on those agents.` +
|
|
184
|
+
(interruption.approvalWaitedMs !== undefined
|
|
185
|
+
? ` The longest single agent wait on a human approval reported for this run was ${interruption.approvalWaitedMs.toLocaleString()}ms` +
|
|
186
|
+
(interruption.approvalWaitedMs > interruption.timeoutMs
|
|
187
|
+
? ` — MORE than the ${interruption.timeoutMs.toLocaleString()}ms window itself, so the deployment's approval clock is` +
|
|
188
|
+
` counting time this run did not (an earlier leg's wait carried forward, or a mis-scaled figure). Read it as` +
|
|
189
|
+
` "waiting dominated", not as a slice of this window.`
|
|
190
|
+
: ` — that much of the ${interruption.timeoutMs.toLocaleString()}ms window was one agent BLOCKED on a person` +
|
|
191
|
+
` rather than working (the longest single wait, not a total).`) +
|
|
192
|
+
` Raising the timeout only helps if that approval gets answered.`
|
|
193
|
+
: ""));
|
|
184
194
|
}
|
|
185
195
|
export function formatWorkflowRun(run) {
|
|
186
196
|
const summary = summarizeWorkflowRun(run);
|
package/dist/core/tool-errors.js
CHANGED
|
@@ -72,6 +72,7 @@ const EXACT_CODE_CLASS = new Map([
|
|
|
72
72
|
["limits.max_walltime_exceeded", "limit"],
|
|
73
73
|
["memory.admission_required", "config"],
|
|
74
74
|
["memory.admission_denied", "config"],
|
|
75
|
+
["memory.capture_optout_denied", "config"],
|
|
75
76
|
["env.lifetime_expired", "config"],
|
|
76
77
|
]);
|
|
77
78
|
const DOTTED_PREFIX_CLASS = [
|
|
@@ -216,6 +216,15 @@ export type PermissionResult = {
|
|
|
216
216
|
* understands (see {@link import("./checkpoint-store.js").ProbeCause}). Engine-stamped and
|
|
217
217
|
* validated at the irreversibility tighten; same display-only posture as the prose sibling. */
|
|
218
218
|
probeCause?: import("./checkpoint-store.js").ProbeCause;
|
|
219
|
+
/** #502 (additive): the reversibility probe declared this demotion STRUCTURAL — see
|
|
220
|
+
* {@link import("./types.js").ReversibilityVerdict.mandated}. Unlike its two `probe*` neighbours
|
|
221
|
+
* this is NOT display metadata: it joins the #144 mandate family, so the persisted-rule lane
|
|
222
|
+
* discloses a matching allow rule as shadowed instead of clearing the ask, and the card's
|
|
223
|
+
* "stop asking me this" offer is withheld. ENGINE-STAMPED at the irreversibility tighten, which
|
|
224
|
+
* constructs this decision wholesale — a policy that self-declares it is overwritten there, and
|
|
225
|
+
* in the one direction the stamp cannot reach (a caller composing its own ask) the claim is
|
|
226
|
+
* tightening-only: it can make an ask outlive a rule, never turn one into an allow. */
|
|
227
|
+
probeMandated?: true;
|
|
219
228
|
/** An EXPLICIT `ask` permission rule matched this call (design/127 DSL — the CC `alwaysAskRules`
|
|
220
229
|
* shape); carries the matched rule's text. Present ⇔ a rule someone WROTE says "ask about this",
|
|
221
230
|
* never for a `defaultAction:"ask"` fallback (an unmatched call is default-closed posture, not a
|
|
@@ -457,6 +466,28 @@ export declare function checkToolPolicyProjection(projection: Pick<ToolPolicyPro
|
|
|
457
466
|
export type ConstraintChainEntryMeta = {
|
|
458
467
|
/** True iff the layer carried a frozen auto-mode classifier (`autoMode`). */
|
|
459
468
|
autoModeArmed?: true;
|
|
469
|
+
/**
|
|
470
|
+
* #503 — the layer's serializable auto-mode ARMING RECIPE: the criteria half of the frozen classifier
|
|
471
|
+
* (assembly inputs + knobs + the deployment's settings epoch), recorded so a CROSS-PROCESS redemption
|
|
472
|
+
* can rebuild an equivalent decider over its own fresh model leg
|
|
473
|
+
* ({@link import("./auto-mode-arming.js").rebuildAutoModeDecider}) instead of answering `unavailable`
|
|
474
|
+
* forever and parking the same approval for a human on every redemption.
|
|
475
|
+
*
|
|
476
|
+
* OPT-IN and ADDITIVE: present only when the arming deployment asked for it (`RunnerDeps.autoMode.
|
|
477
|
+
* persistArming`), so an unchanged deployment mints byte-identical entries and every pre-#503 row
|
|
478
|
+
* (and every row minted without the opt-in) reads as absence = the pre-#503 behavior. Absence is also
|
|
479
|
+
* what an OPEN BREAKER produces — see {@link constraintChainEntryOfLayer}.
|
|
480
|
+
*
|
|
481
|
+
* The digest binds it: a re-supply that DROPS the recorded recipe, or hands back a DIFFERENT one, is
|
|
482
|
+
* rejected pre-CAS — so the re-supply contract is exactly "hand back what the row carries" (a redeeming
|
|
483
|
+
* host reads the recipe off this field and passes it straight through). The verify side takes the
|
|
484
|
+
* PRESENCE of this field from the row, never from the re-supplied layer's live classifier health, so a
|
|
485
|
+
* row that recorded no recipe does not refuse a re-supply that offers one — the reasoning, and why that
|
|
486
|
+
* direction cannot be recovered, is recorded on {@link constraintChainEntryOfSuppliedLayer}.
|
|
487
|
+
* `checkToolPolicyProjection` never reads it: like the other metadata here it exists to be digested,
|
|
488
|
+
* not to execute.
|
|
489
|
+
*/
|
|
490
|
+
autoModeArming?: import("./auto-mode-arming.js").AutoModeArmingRecipe;
|
|
460
491
|
durableMandate?: true;
|
|
461
492
|
contentMandate?: true;
|
|
462
493
|
};
|
|
@@ -469,6 +500,105 @@ export type ConstraintChainEntry = ConstraintChainEntryMeta & ({
|
|
|
469
500
|
/** Project one policy into its chain entry (opaque placeholder when it exports no projection),
|
|
470
501
|
* carrying the layer's decision-chain metadata posture for the digest (round 12). */
|
|
471
502
|
export declare function constraintChainEntryOf(policy: ToolPolicy, meta?: ConstraintChainEntryMeta): ConstraintChainEntry;
|
|
503
|
+
/**
|
|
504
|
+
* One inherited-chain LAYER as both chain-entry mint sites see it — structurally an
|
|
505
|
+
* `InheritedGate.parentConstraints[]` element, typed here structurally so this module (which owns the
|
|
506
|
+
* digest) does not depend on the runner's shapes.
|
|
507
|
+
*/
|
|
508
|
+
export interface ConstraintChainLayerView {
|
|
509
|
+
policy: ToolPolicy;
|
|
510
|
+
autoMode?: {
|
|
511
|
+
decider: {
|
|
512
|
+
breakerOpen: () => boolean;
|
|
513
|
+
consecutiveFailures?: () => number;
|
|
514
|
+
};
|
|
515
|
+
arming?: unknown;
|
|
516
|
+
};
|
|
517
|
+
durableMandate?: boolean;
|
|
518
|
+
contentMandate?: boolean;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* The WRITE-side mint: the chain entry a durable PARK records for a live inherited layer.
|
|
522
|
+
*
|
|
523
|
+
* #503 safety bound ① lives HERE, at the persistence point rather than at arming time: the recipe is
|
|
524
|
+
* carried only while the layer's breaker is UNTRIPPED AND UNTOUCHED. An open breaker is the ancestor
|
|
525
|
+
* session's one-way "fell back to non-auto" latch — its arming has been withdrawn, and handing that
|
|
526
|
+
* arming to a redemption would let the redeemed leg run auto-classified where the ancestor no longer does
|
|
527
|
+
* (a redemption WIDER than the ancestor it inherits). The latch is read at the moment the row is written,
|
|
528
|
+
* so an ancestor whose classifier died mid-run parks WITHOUT a recipe even though it was armed when the
|
|
529
|
+
* chain was assembled.
|
|
530
|
+
*
|
|
531
|
+
* `breakerOpen()` alone is NOT the whole latch, and a closed-only test left a real widening: a rebuilt
|
|
532
|
+
* decider starts a FRESH failure streak, so an ancestor sitting at 2 failures out of 3 — one bad
|
|
533
|
+
* classification from dropping the whole session to non-auto — would hand a redemption the full budget
|
|
534
|
+
* again, and the redeemed leg stays auto-classified through failures the ancestor would not have
|
|
535
|
+
* survived. So the streak must read ZERO as well. A decider that does not expose it at all is UNKNOWN
|
|
536
|
+
* state, and unknown takes the same arm as tripped: nothing is recorded (which is why a host that hand
|
|
537
|
+
* rolls a decider cannot use the persisted-arming path — it must mint through `createAutoModeDecider`,
|
|
538
|
+
* on both the arming and the redeeming side).
|
|
539
|
+
*
|
|
540
|
+
* Every read here is guarded, and every failure to read is treated as "withdrawn" — the opposite default
|
|
541
|
+
* from the gate's demote-to-ask breaker reader, and for the same reason both are right: there, an
|
|
542
|
+
* unreadable latch must not suppress a tighten; here, it must not license a carry-forward.
|
|
543
|
+
*
|
|
544
|
+
* ⚠️ WRITE SIDE ONLY. This gate reads MUTABLE state (a one-way latch, plus a streak that both grows and
|
|
545
|
+
* resets to zero on any healthy classification). The resume re-supply must NOT re-run it — it samples a
|
|
546
|
+
* different instant, and the two instants disagree for perfectly ordinary reasons: an ancestor keeps
|
|
547
|
+
* running while its delegated child sits parked awaiting a human. See
|
|
548
|
+
* {@link constraintChainEntryOfSuppliedLayer} for the verify side and the full account.
|
|
549
|
+
*
|
|
550
|
+
* ⚖️ WHAT THIS BOUND DOES NOT DO (recorded, precise shape). A withdrawn arming leaves the entry at
|
|
551
|
+
* `autoModeArmed: true` with no recipe — digest-identical to an armed layer whose deployment never opted
|
|
552
|
+
* in. So the digest does not, and cannot, stop a re-supplier from handing back a FRESH healthy decider of
|
|
553
|
+
* its own: that is the pre-existing re-supply trust posture (see `resumeStream`'s contract — a caller
|
|
554
|
+
* able to invoke it already holds full Runner authority, and the chain checks catch shape MISTAKES, not
|
|
555
|
+
* a hostile trusted caller), and it is unchanged here. What this bound governs is what CORE hands
|
|
556
|
+
* forward: a withdrawn ancestor's CRITERIA never travel, so a redemption cannot reconstruct the
|
|
557
|
+
* ancestor's classifier from the row. For a fleet running `persistArming` everywhere, the pair
|
|
558
|
+
* (`autoModeArmed` present, `autoModeArming` absent) is itself the readable signal that the ancestor's
|
|
559
|
+
* arming was withdrawn or unrecordable, and such a host should re-supply a refusing decider rather than a
|
|
560
|
+
* fresh one. Making the WITHDRAWAL itself digest-bound is a coherent stronger design — it needs a new
|
|
561
|
+
* marker plus a new re-supply obligation to reproduce it (a withdrawn row carries no recipe, so the
|
|
562
|
+
* marker cannot be derived from one), which is a scope decision for this seam's owner, not a silent
|
|
563
|
+
* mechanism change.
|
|
564
|
+
*/
|
|
565
|
+
export declare function constraintChainEntryOfLayer(layer: ConstraintChainLayerView): ConstraintChainEntry;
|
|
566
|
+
/**
|
|
567
|
+
* The VERIFY-side mint: the entry a resume's RE-SUPPLIED layer must reproduce for the row it is
|
|
568
|
+
* redeeming. Same body as the park mint, with the arming carry taken from the ROW rather than from the
|
|
569
|
+
* layer's live classifier health — so the digest equality is over bytes the row itself recorded, and
|
|
570
|
+
* over nothing that can move while the row sits parked.
|
|
571
|
+
*
|
|
572
|
+
* WHY THE CARRY CANNOT BE RE-DERIVED HERE. Safety bound ① (see {@link constraintChainEntryOfLayer}) is a
|
|
573
|
+
* statement about the ancestor at the instant the row was WRITTEN. Re-running it against the same live
|
|
574
|
+
* layer at redemption time samples a different instant, and both directions of the disagreement strand an
|
|
575
|
+
* approval a human already granted:
|
|
576
|
+
* - the ancestor was healthy at the park (the row carries the recipe) and its breaker opened while the
|
|
577
|
+
* child sat parked ⇒ the re-supply mints WITHOUT the recipe, the digests differ, and because the
|
|
578
|
+
* latch is ONE-WAY the row is refused `resume.parent_constraint_mismatch` forever;
|
|
579
|
+
* - the ancestor was one failure into its streak at the park (the row carries no recipe) and the streak
|
|
580
|
+
* cleared on its next healthy classification ⇒ the re-supply mints WITH a recipe the row never
|
|
581
|
+
* carried, same refusal.
|
|
582
|
+
* Neither needs a host mistake: the ancestor and its siblings keep running while the child is parked, and
|
|
583
|
+
* the same-process `resume()` verb re-supplies the very same live layer objects out of the Runner's own
|
|
584
|
+
* parked-constraint registry. Nothing about the row changed; only a classifier's health did.
|
|
585
|
+
*
|
|
586
|
+
* WHAT THIS COSTS, NAMED. A row that recorded NO recipe no longer refuses a re-supply that hands one
|
|
587
|
+
* back. That is not a tunable — the re-supply side literally cannot tell "the row withdrew a recipe this
|
|
588
|
+
* same live layer still declares" (the second bullet above, a legitimate redemption) from "a re-supplier
|
|
589
|
+
* is putting a recipe onto a row that withdrew one": both are the identical pair of inputs. It is also
|
|
590
|
+
* thinner than it looks. The recipe is not an enforcement object — the DECIDER classifies, and the
|
|
591
|
+
* re-supply trust posture already permits handing back a fresh healthy one, which is the capability that
|
|
592
|
+
* would matter. What the row's absence still means is unchanged and is the property bound ① was written
|
|
593
|
+
* for: a withdrawn ancestor's criteria never travel ON THE ROW, so no redemption can reconstruct that
|
|
594
|
+
* ancestor's classifier from the checkpoint.
|
|
595
|
+
*
|
|
596
|
+
* WHAT THE DIGEST STILL BINDS. A row that DID record a recipe must have that recipe handed back: the
|
|
597
|
+
* carry is on, so a re-supply that drops the arming, or hands back a different one, mints a different
|
|
598
|
+
* entry and is refused pre-CAS exactly as before. That is the whole of the documented re-supply contract
|
|
599
|
+
* — "hand back what the row carries".
|
|
600
|
+
*/
|
|
601
|
+
export declare function constraintChainEntryOfSuppliedLayer(layer: ConstraintChainLayerView, recorded: ConstraintChainEntry | undefined): ConstraintChainEntry;
|
|
472
602
|
/**
|
|
473
603
|
* Content digest over an ordered constraint chain (F-012 L1) — the durable half's anti-tamper anchor.
|
|
474
604
|
* Binds the chain TOPOLOGY (order, layer boundaries, opaque placeholders) and each projection's full
|
|
@@ -900,7 +1030,10 @@ export interface AskRequest {
|
|
|
900
1030
|
* substitution yields NO offer, which is the honest answer rather than an option that would be
|
|
901
1031
|
* refused on redemption) AND the ask is one a persisted rule could actually clear — a mandated ask
|
|
902
1032
|
* (operator shellGate:"always", the tool's own egress/irreversibility marks, a
|
|
903
|
-
* `requiresRealApproval` demand
|
|
1033
|
+
* `requiresRealApproval` demand, or — #502, the one PER-CALL member of that family — a demotion the
|
|
1034
|
+
* call's own reversibility probe declared structural, which for the built-in shell probe means a read
|
|
1035
|
+
* outside the session's allowed directories) and an ask carrying {@link persistedRuleShadowed} offer
|
|
1036
|
+
* none.
|
|
904
1037
|
*
|
|
905
1038
|
* CONTRACT — see {@link import("./permission-rule-model.js").RuleOffer} for the closed union, the
|
|
906
1039
|
* per-element parse/degrade rules and the original-index-keying obligation. Array order is display
|
|
@@ -919,6 +1052,44 @@ export interface AskRequest {
|
|
|
919
1052
|
* from it across a coverage change — harmlessly, since the record is what gets confirmed.
|
|
920
1053
|
*/
|
|
921
1054
|
readonly ruleOffers?: readonly import("./permission-rule-model.js").RuleOffer[];
|
|
1055
|
+
/**
|
|
1056
|
+
* #490 修② — WHY {@link ruleOffers} is absent, when the rule-offer lane is in play and has nothing
|
|
1057
|
+
* to give. A CLOSED set, mutually exclusive with {@link ruleOffers} (never both, never neither once
|
|
1058
|
+
* the lane is in play):
|
|
1059
|
+
* · `"mandated"` — no persisted rule could ever clear this ask: an org/governance
|
|
1060
|
+
* `requiresRealApproval` demand, an operator `shellGate:"always"` or the tool's own
|
|
1061
|
+
* egress/irreversibility marks, a hook-raised ask, an ask whose authority is an ANCESTOR's
|
|
1062
|
+
* approver rather than this task's rule lane, or (#502) a demotion this CALL's reversibility
|
|
1063
|
+
* probe declared structural — for the built-in shell probe, a read outside the directories this
|
|
1064
|
+
* session declared. A surface must NOT point at rule-writing here — "allow rules silence the
|
|
1065
|
+
* classifier's questions, never a mandated one", so a rule minted in answer to this line would
|
|
1066
|
+
* never take effect where it was minted. It must not point at a directory GRANT either: the
|
|
1067
|
+
* out-of-root arm has no clearing configuration in this version, and confirming the call is the
|
|
1068
|
+
* whole of what a person can do about it;
|
|
1069
|
+
* · `"shadowed"` — a rule the person already wrote is speaking and does not clear this ask (a
|
|
1070
|
+
* matching allow rule that the lane refuses to honor here, or their own explicit `ask` rule);
|
|
1071
|
+
* · `"lane_cannot_speak"` — the lane is armed and this ask IS clearable, but the offer lane
|
|
1072
|
+
* produced no rule for this command. Usually because the grammar has no text for it (a
|
|
1073
|
+
* substitution, a subshell, a backgrounded process, or a simple command carrying a redirection —
|
|
1074
|
+
* a compound is judged per segment, so this arm means EVERY segment was unspeakable, since one
|
|
1075
|
+
* dirty segment among clean ones yields a batch instead). It also covers the narrower case where a
|
|
1076
|
+
* text EXISTS but the card's own array-order contract declines to present it alone (a simple
|
|
1077
|
+
* command whose exact form the validator refuses offers nothing rather than promoting a lone
|
|
1078
|
+
* prefix to index 0 — see `suggestRulesForCommand`). The seat says "this card has no rule to
|
|
1079
|
+
* offer you", which is true of both; it does not claim the grammar is empty, and a surface must
|
|
1080
|
+
* not word it as though no rule could ever be written for the command.
|
|
1081
|
+
*
|
|
1082
|
+
* ABSENT is not a claim, exactly as {@link previewWithheld}'s absence is not: it also covers the
|
|
1083
|
+
* ordinary "there are offers" case and the three STRUCTURAL doors that answer with silence on
|
|
1084
|
+
* purpose (no rule lane wired, a tool the lane does not speak for, a task that cannot hold a rule at
|
|
1085
|
+
* all) — a reason line on every ask of a deployment that never had the feature would be noise about
|
|
1086
|
+
* a lane that does not exist. Read presence, never absence.
|
|
1087
|
+
*
|
|
1088
|
+
* ADVISORY disclosure metadata, never adjudication input: it changes nothing about what is allowed,
|
|
1089
|
+
* and a surface that ignores it renders exactly the card it rendered before. The durable park row
|
|
1090
|
+
* carries the same seat under the same contract, filled by the same one factory.
|
|
1091
|
+
*/
|
|
1092
|
+
readonly ruleOffersAbsence?: "mandated" | "lane_cannot_speak" | "shadowed";
|
|
922
1093
|
/** The human/model-readable prompt explaining what needs approval. */
|
|
923
1094
|
message: string;
|
|
924
1095
|
/** Runner-filled source identity (design S1d §1): the end-user {@link AskRequest} issuing task's
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { brandPolicyAskClass } from "./ask-class.js";
|
|
4
|
+
import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
|
|
4
5
|
import { join, normalize as normalizePath, posix as posixPath, sep, win32 as winPath } from "node:path";
|
|
5
6
|
import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
|
|
6
7
|
import { boundInputHashOf } from "./canonical-json.js";
|
|
@@ -85,6 +86,7 @@ export function checkToolPolicyProjection(projection, req) {
|
|
|
85
86
|
export function constraintChainEntryOf(policy, meta) {
|
|
86
87
|
const m = {
|
|
87
88
|
...(meta?.autoModeArmed ? { autoModeArmed: true } : {}),
|
|
89
|
+
...(meta?.autoModeArming !== undefined ? { autoModeArming: meta.autoModeArming } : {}),
|
|
88
90
|
...(meta?.durableMandate ? { durableMandate: true } : {}),
|
|
89
91
|
...(meta?.contentMandate ? { contentMandate: true } : {}),
|
|
90
92
|
};
|
|
@@ -93,6 +95,31 @@ export function constraintChainEntryOf(policy, meta) {
|
|
|
93
95
|
return { opaque: true, ...m };
|
|
94
96
|
return { components: p.components, requiresLiveRemainder: p.requiresLiveRemainder, ...m };
|
|
95
97
|
}
|
|
98
|
+
function chainEntryOfLayerCarrying(layer, carryArming) {
|
|
99
|
+
const arming = carryArming ? sanitizeAutoModeArmingRecipe(layer.autoMode?.arming) : undefined;
|
|
100
|
+
return constraintChainEntryOf(layer.policy, {
|
|
101
|
+
...(layer.autoMode !== undefined ? { autoModeArmed: true } : {}),
|
|
102
|
+
...(arming !== undefined ? { autoModeArming: arming } : {}),
|
|
103
|
+
...(layer.durableMandate === true ? { durableMandate: true } : {}),
|
|
104
|
+
...(layer.contentMandate === true ? { contentMandate: true } : {}),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
export function constraintChainEntryOfLayer(layer) {
|
|
108
|
+
let armingLive = false;
|
|
109
|
+
if (layer.autoMode?.arming !== undefined) {
|
|
110
|
+
try {
|
|
111
|
+
const d = layer.autoMode.decider;
|
|
112
|
+
armingLive = d.breakerOpen() !== true && typeof d.consecutiveFailures === "function" && d.consecutiveFailures() === 0;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
armingLive = false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return chainEntryOfLayerCarrying(layer, armingLive);
|
|
119
|
+
}
|
|
120
|
+
export function constraintChainEntryOfSuppliedLayer(layer, recorded) {
|
|
121
|
+
return chainEntryOfLayerCarrying(layer, recorded?.autoModeArming !== undefined);
|
|
122
|
+
}
|
|
96
123
|
function stableJson(v) {
|
|
97
124
|
if (v === null || typeof v !== "object")
|
|
98
125
|
return JSON.stringify(v) ?? "null";
|
|
@@ -354,6 +381,7 @@ export function combinePolicies(...policies) {
|
|
|
354
381
|
let rewrite;
|
|
355
382
|
let settled;
|
|
356
383
|
let ruleAskText;
|
|
384
|
+
let probeMandateSeen = false;
|
|
357
385
|
for (const p of policies) {
|
|
358
386
|
const d = refuseOutOfContractDecision(await p.check(current, signal));
|
|
359
387
|
if (d.action === "deny") {
|
|
@@ -381,10 +409,13 @@ export function combinePolicies(...policies) {
|
|
|
381
409
|
if (d.action === "ask" && d.matchedAskRule !== undefined && ruleAskText === undefined) {
|
|
382
410
|
ruleAskText = d.matchedAskRule;
|
|
383
411
|
}
|
|
412
|
+
if (d.action === "ask" && d.probeMandated === true)
|
|
413
|
+
probeMandateSeen = true;
|
|
384
414
|
}
|
|
385
415
|
if (asked) {
|
|
386
416
|
const merged = rewrite?.updatedInput;
|
|
387
|
-
const
|
|
417
|
+
const withRuleAsk = ruleAskText !== undefined && asked.matchedAskRule === undefined ? { ...asked, matchedAskRule: ruleAskText } : asked;
|
|
418
|
+
const withMark = probeMandateSeen && withRuleAsk.probeMandated !== true ? { ...withRuleAsk, probeMandated: true } : withRuleAsk;
|
|
388
419
|
return merged !== undefined ? { ...withMark, updatedInput: merged } : withMark;
|
|
389
420
|
}
|
|
390
421
|
const allowed = rewrite ?? ALLOW;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
+
import { uuidv7 } from "../engine/session/uuid.js";
|
|
3
4
|
import { defineTool, errorResult } from "./tools.js";
|
|
4
5
|
import { deliverEngineNotice } from "./types.js";
|
|
5
6
|
import { TOOL_SEARCH_NAME } from "./runner/tool-disclosure.js";
|
|
@@ -178,7 +179,7 @@ export function createOffloadPersist(store, sessionId, onNotice) {
|
|
|
178
179
|
const message = `tool-result offload: persisting ref "${ref}" failed (${cause}) — this write attempt stored nothing; ` +
|
|
179
180
|
`unless an earlier attempt already stored this ref, the inline preview is all that survived. ` +
|
|
180
181
|
`A later re-clear of the same result retries the write.`;
|
|
181
|
-
deliverEngineNotice(onNotice, { code: "tool_result.offload_put_failed", message, detail: { ref, sessionId, cause } });
|
|
182
|
+
deliverEngineNotice(onNotice, { code: "tool_result.offload_put_failed", message, detail: { ref, sessionId, cause, occurrenceId: uuidv7() } });
|
|
182
183
|
};
|
|
183
184
|
try {
|
|
184
185
|
void Promise.resolve(store.put(ref, fullText, provenance)).catch(reportFailedWrite);
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -58,6 +58,23 @@ export type TraceEvent = {
|
|
|
58
58
|
kind: "task.start";
|
|
59
59
|
version: 1;
|
|
60
60
|
taskId: string;
|
|
61
|
+
/** #499 (additive) — the ENGINE-minted identity of THIS run (`TaskResult.runId`). `taskId` is
|
|
62
|
+
* `spec.taskId ?? sessionId`, so two runs of one session that declare no host task id emit two
|
|
63
|
+
* task.start frames under ONE id and a consumer pairing start↔end has no way to say which end
|
|
64
|
+
* closes which start. Present on every `task.start` (the frame is emitted after prepare, which
|
|
65
|
+
* is where the id is minted).
|
|
66
|
+
*
|
|
67
|
+
* PAIRING CONTRACT, stated in full because it is not symmetric: a `task.end` may carry a
|
|
68
|
+
* `runId` this frame never announced — a run whose PREPARE threw emits the end and no start
|
|
69
|
+
* (there was nothing to start), and that end still names its run because prepare-time
|
|
70
|
+
* disclosures already did. So: every start has an end; not every end has a start. A consumer
|
|
71
|
+
* building spans must treat an unmatched end as a run that never began, not as a lost start.
|
|
72
|
+
*
|
|
73
|
+
* SCOPE, stated so it is not mistaken for more: only this frame and `task.end` carry the run
|
|
74
|
+
* axis today. Every other frame in this union still keys on `taskId` alone, so frames from two
|
|
75
|
+
* CONCURRENT runs that share a host `taskId` remain mutually indistinguishable inside their
|
|
76
|
+
* windows. Extending the axis across the union is a separate pass. */
|
|
77
|
+
runId?: string;
|
|
61
78
|
model: string;
|
|
62
79
|
/** R4 (1.285): the engine build (package.json version, runtime-resolved) that produced this
|
|
63
80
|
* run — binds trace forensics to an exact build. "unknown" if resolution failed. */
|
|
@@ -247,6 +264,13 @@ export type TraceEvent = {
|
|
|
247
264
|
kind: "task.end";
|
|
248
265
|
version: 1;
|
|
249
266
|
taskId: string;
|
|
267
|
+
/** #499 (additive) — the run this terminal closes; pairs with the `task.start` frame's `runId`
|
|
268
|
+
* (see that field for why `taskId` cannot carry the pairing, and for the ASYMMETRY: this frame
|
|
269
|
+
* is also emitted on the prepare-throw backstop, where no `task.start` exists — it still names
|
|
270
|
+
* its run, because the id is minted at prepare's first statement and prepare-time disclosures
|
|
271
|
+
* already carry it). Absent only where prepare was never entered at all, which is the same
|
|
272
|
+
* condition its `taskId: "unknown"` states. */
|
|
273
|
+
runId?: string;
|
|
250
274
|
status: TaskStatus;
|
|
251
275
|
errorCode?: string;
|
|
252
276
|
turns: number;
|
|
@@ -425,7 +449,10 @@ export type TraceEvent = {
|
|
|
425
449
|
/** The bucket this round synced. A local-owner bucket cannot sync, so this is always a principal. */
|
|
426
450
|
principal: string;
|
|
427
451
|
rule: string;
|
|
428
|
-
|
|
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";
|
|
429
456
|
ts: number;
|
|
430
457
|
} | {
|
|
431
458
|
/**
|
|
@@ -438,7 +465,9 @@ export type TraceEvent = {
|
|
|
438
465
|
version: 1;
|
|
439
466
|
principal: string;
|
|
440
467
|
rule: string;
|
|
441
|
-
|
|
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";
|
|
442
471
|
/** Closed reason code ({@link import("./governance-codes.js").RuleSyncDropReason}). */
|
|
443
472
|
reason: string;
|
|
444
473
|
ts: number;
|