@sema-agent/core 7.12.0 → 7.14.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 +124 -0
- package/dist/agents/subagent.d.ts +4 -9
- package/dist/agents/subagent.js +16 -12
- package/dist/agents/teacher.js +1 -1
- package/dist/brain/reasoning.d.ts +23 -0
- package/dist/brain/reasoning.js +35 -4
- package/dist/brain/stream-engine.js +35 -17
- package/dist/brain/timeout.d.ts +25 -4
- package/dist/brain/timeout.js +1 -1
- package/dist/core/ask-origin.d.ts +83 -9
- package/dist/core/ask-origin.js +31 -5
- package/dist/core/auto-mode.d.ts +3 -3
- package/dist/core/checkpoint-seat.d.ts +18 -0
- package/dist/core/checkpoint-seat.js +6 -0
- package/dist/core/checkpoint-store.d.ts +9 -16
- package/dist/core/checkpoint-store.js +7 -4
- package/dist/core/engine-notice.d.ts +21 -1
- package/dist/core/env-knob-announce.d.ts +16 -0
- package/dist/core/env-knob-announce.js +14 -0
- package/dist/core/gate-fold.js +2 -0
- package/dist/core/gate-lanes.d.ts +0 -40
- package/dist/core/gate-lanes.js +24 -29
- package/dist/core/gate-outcome.d.ts +42 -3
- package/dist/core/gate-outcome.js +22 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +3 -1
- package/dist/core/hooks.d.ts +6 -17
- package/dist/core/hooks.js +7 -5
- package/dist/core/mcp.d.ts +3 -2
- package/dist/core/mcp.js +5 -11
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/runner/advertised-writable-dirs.d.ts +74 -0
- package/dist/core/runner/advertised-writable-dirs.js +48 -0
- package/dist/core/runner/assemble-result.d.ts +50 -1
- package/dist/core/runner/assemble-result.js +4 -1
- package/dist/core/runner/compaction-seams.d.ts +42 -0
- package/dist/core/runner/compaction-seams.js +80 -0
- package/dist/core/runner/contracts.d.ts +44 -6
- package/dist/core/runner/denial-limit-arms.d.ts +7 -9
- package/dist/core/runner/denial-limit-arms.js +11 -12
- package/dist/core/runner/gate-exit.d.ts +15 -3
- package/dist/core/runner/gate-exit.js +6 -4
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -1
- package/dist/core/runner/permission-rule-lanes.js +2 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +34 -4
- package/dist/core/runner/prepare-caps-and-workflow.js +47 -12
- package/dist/core/runner/prepare-gate-stations.d.ts +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +10 -4
- package/dist/core/runner/prepare-hands-readface.js +5 -14
- package/dist/core/runner/prepare-inherited-gate.d.ts +5 -3
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +31 -34
- package/dist/core/runner/prepare-memory.js +73 -51
- package/dist/core/runner/prepare-policy-chain.js +8 -4
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -0
- package/dist/core/runner/prepare-prompt-inputs.js +2 -2
- package/dist/core/runner/prepare-protocol-tools.js +5 -0
- package/dist/core/runner/prepare-question-face.d.ts +10 -8
- package/dist/core/runner/prepare-question-face.js +1 -3
- package/dist/core/runner/prepare-safety-scan.js +11 -16
- package/dist/core/runner/prepare-task.d.ts +9 -1
- package/dist/core/runner/prepare-task.js +63 -49
- package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
- package/dist/core/runner/prepare-wiring-manifest.js +1 -1
- package/dist/core/runner/resume-claim.d.ts +2 -2
- package/dist/core/runner/resume-preflight.d.ts +2 -2
- package/dist/core/runner/run-attachment-seats.d.ts +2 -2
- package/dist/core/runner/run-compaction-machinery.js +2 -0
- package/dist/core/runner/run-git-lane.d.ts +1 -1
- package/dist/core/runner/run-harness-handlers.js +5 -1
- package/dist/core/runner/run-identity-wiring.d.ts +7 -0
- package/dist/core/runner/run-identity-wiring.js +3 -1
- package/dist/core/runner/run-leg.d.ts +7 -5
- package/dist/core/runner/run-leg.js +256 -5
- package/dist/core/runner/run-notification-lane.d.ts +4 -3
- package/dist/core/runner/run-recovery-lanes.d.ts +4 -17
- package/dist/core/runner/run-recovery-lanes.js +5 -4
- package/dist/core/runner/run-settle-and-teardown.d.ts +14 -10
- package/dist/core/runner/run-settle-and-teardown.js +112 -4
- package/dist/core/runner/run-stop-and-final-verify.d.ts +2 -2
- package/dist/core/runner/run-terminal-adoption.d.ts +16 -16
- package/dist/core/runner/run-terminal-adoption.js +87 -7
- package/dist/core/runner/run-turn-boundary.js +5 -4
- package/dist/core/runner/runtask.d.ts +10 -90
- package/dist/core/runner/runtask.js +77 -596
- package/dist/core/runner/stream-lifecycle-verbs.js +10 -1
- package/dist/core/runner/stream-settle-backstop.js +1 -1
- package/dist/core/runtime-caps.d.ts +21 -0
- package/dist/core/runtime-caps.js +5 -1
- package/dist/core/task-event.d.ts +11 -2
- package/dist/core/task-registry-shared.js +8 -6
- package/dist/core/task-result.d.ts +15 -0
- package/dist/core/task-stream.d.ts +10 -1
- package/dist/core/terminal-cause.d.ts +6 -2
- package/dist/core/tool-face.d.ts +8 -0
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +39 -31
- package/dist/core/tool-policy.js +29 -5
- package/dist/core/tool-registry.d.ts +11 -3
- package/dist/core/tool-registry.js +7 -1
- package/dist/core/tool-roster.d.ts +26 -0
- package/dist/core/tool-roster.js +40 -7
- package/dist/core/tool-spec.d.ts +13 -12
- package/dist/core/types.d.ts +2 -1
- package/dist/core/types.js +1 -0
- package/dist/core/wiring-manifest.d.ts +16 -9
- package/dist/core/wiring-manifest.js +8 -3
- package/dist/index.d.ts +8 -7
- package/dist/index.js +7 -6
- package/dist/orchestration/run-workflow-tool.d.ts +8 -8
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +4 -3
- package/dist/orchestration/workflow-primitives.js +3 -3
- package/dist/orchestration/workflow-types.d.ts +14 -0
- package/dist/orchestration/workflow.d.ts +41 -4
- package/dist/orchestration/workflow.js +23 -5
- package/dist/server/http.js +2 -1
- package/dist/tools/fs/fs-shared.js +9 -3
- package/dist/tools/fs/read-deny.d.ts +24 -8
- package/dist/tools/fs/read-deny.js +20 -1
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +35 -1
|
@@ -246,6 +246,11 @@ async function sleepAnnouncingRetry(totalMs, signal, frame) {
|
|
|
246
246
|
return;
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
|
+
function midstreamRetryAnnouncement(failure) {
|
|
250
|
+
return failure.kind === "stalled"
|
|
251
|
+
? { errClass: "stall", detail: `${failure.watchdog} watchdog fired after ${failure.timeoutMs}ms (${failure.knob}), retrying` }
|
|
252
|
+
: { errClass: "transport", detail: "connection lost, reconnecting" };
|
|
253
|
+
}
|
|
249
254
|
export function runStreamingBrain(args) {
|
|
250
255
|
const { model, doFetch, signal, config, httpLabel, buildRequest, makeParser, stallTimeouts } = args;
|
|
251
256
|
const out = createAssistantMessageEventStream();
|
|
@@ -471,7 +476,7 @@ export function runStreamingBrain(args) {
|
|
|
471
476
|
const decoder = new TextDecoder();
|
|
472
477
|
let buffer = "";
|
|
473
478
|
let firstTokenSeen = false;
|
|
474
|
-
let
|
|
479
|
+
let firedFirstTokenDeadlineMs;
|
|
475
480
|
let ftTimer;
|
|
476
481
|
let waitTimer;
|
|
477
482
|
const clearWaitTimer = () => {
|
|
@@ -482,7 +487,7 @@ export function runStreamingBrain(args) {
|
|
|
482
487
|
};
|
|
483
488
|
if (firstTokenTimeoutMs && firstTokenTimeoutMs > 0) {
|
|
484
489
|
ftTimer = setTimeout(() => {
|
|
485
|
-
|
|
490
|
+
firedFirstTokenDeadlineMs = firstTokenTimeoutMs;
|
|
486
491
|
clearWaitTimer();
|
|
487
492
|
void reader.cancel().catch(() => undefined);
|
|
488
493
|
}, firstTokenTimeoutMs);
|
|
@@ -492,7 +497,7 @@ export function runStreamingBrain(args) {
|
|
|
492
497
|
const scheduleWaitFrame = (delayMs) => {
|
|
493
498
|
waitTimer = setTimeout(() => {
|
|
494
499
|
waitTimer = undefined;
|
|
495
|
-
if (firstTokenSeen ||
|
|
500
|
+
if (firstTokenSeen || firedFirstTokenDeadlineMs !== undefined || Date.now() - waitStartedAt >= firstTokenTimeoutMs)
|
|
496
501
|
return;
|
|
497
502
|
announcedWait = true;
|
|
498
503
|
emitWaitFrame({
|
|
@@ -507,16 +512,16 @@ export function runStreamingBrain(args) {
|
|
|
507
512
|
scheduleWaitFrame(WAITING_FIRST_TOKEN_AFTER_MS);
|
|
508
513
|
}
|
|
509
514
|
}
|
|
510
|
-
let
|
|
515
|
+
let firedIdleDeadlineMs;
|
|
511
516
|
let idleTimer;
|
|
512
517
|
const bumpIdle = () => {
|
|
513
518
|
if (!idleTimeoutMs || idleTimeoutMs <= 0)
|
|
514
519
|
return;
|
|
515
|
-
|
|
520
|
+
firedIdleDeadlineMs = undefined;
|
|
516
521
|
if (idleTimer)
|
|
517
522
|
clearTimeout(idleTimer);
|
|
518
523
|
idleTimer = setTimeout(() => {
|
|
519
|
-
|
|
524
|
+
firedIdleDeadlineMs = idleTimeoutMs;
|
|
520
525
|
void reader.cancel().catch(() => undefined);
|
|
521
526
|
}, idleTimeoutMs);
|
|
522
527
|
};
|
|
@@ -525,7 +530,7 @@ export function runStreamingBrain(args) {
|
|
|
525
530
|
if (firstTokenSeen)
|
|
526
531
|
return;
|
|
527
532
|
firstTokenSeen = true;
|
|
528
|
-
|
|
533
|
+
firedFirstTokenDeadlineMs = undefined;
|
|
529
534
|
if (ftTimer) {
|
|
530
535
|
clearTimeout(ftTimer);
|
|
531
536
|
ftTimer = undefined;
|
|
@@ -615,11 +620,23 @@ export function runStreamingBrain(args) {
|
|
|
615
620
|
clearTimeout(idleTimer);
|
|
616
621
|
idleTimer = undefined;
|
|
617
622
|
}
|
|
618
|
-
if (!failure &&
|
|
619
|
-
failure = {
|
|
623
|
+
if (!failure && firedFirstTokenDeadlineMs !== undefined) {
|
|
624
|
+
failure = {
|
|
625
|
+
kind: "stalled",
|
|
626
|
+
watchdog: "first-token",
|
|
627
|
+
knob: "firstTokenTimeoutMs",
|
|
628
|
+
timeoutMs: firedFirstTokenDeadlineMs,
|
|
629
|
+
err: new BrainError("network", `first-token timeout after ${firedFirstTokenDeadlineMs}ms`, undefined, { apiError: true }),
|
|
630
|
+
};
|
|
620
631
|
}
|
|
621
|
-
if (!failure &&
|
|
622
|
-
failure = {
|
|
632
|
+
if (!failure && firedIdleDeadlineMs !== undefined) {
|
|
633
|
+
failure = {
|
|
634
|
+
kind: "stalled",
|
|
635
|
+
watchdog: "stream idle",
|
|
636
|
+
knob: "idleTimeoutMs",
|
|
637
|
+
timeoutMs: firedIdleDeadlineMs,
|
|
638
|
+
err: new BrainError("network", `stream idle timeout after ${firedIdleDeadlineMs}ms`, undefined, { apiError: true }),
|
|
639
|
+
};
|
|
623
640
|
}
|
|
624
641
|
if (!failure) {
|
|
625
642
|
buffer += decoder.decode();
|
|
@@ -641,22 +658,23 @@ export function runStreamingBrain(args) {
|
|
|
641
658
|
throw failure.err;
|
|
642
659
|
cleanup?.();
|
|
643
660
|
cleanup = undefined;
|
|
661
|
+
const announcement = midstreamRetryAnnouncement(failure);
|
|
644
662
|
if (snap.hasOnlyThinking) {
|
|
645
663
|
if (thinkingRetries >= THINKING_RETRY_BUDGET)
|
|
646
664
|
throw failure.err;
|
|
647
665
|
thinkingRetries++;
|
|
648
666
|
parser.sealForRetry();
|
|
649
667
|
const delayMs = 100 * thinkingRetries;
|
|
650
|
-
emitBrainTelemetry({ kind: "retry", attempt: thinkingRetries, phase: "midstream", errClass:
|
|
668
|
+
emitBrainTelemetry({ kind: "retry", attempt: thinkingRetries, phase: "midstream", errClass: announcement.errClass, nextDelayMs: delayMs });
|
|
651
669
|
announcedRetry = true;
|
|
652
670
|
await sleepAnnouncingRetry(delayMs, signal, (remainingMs) => ({
|
|
653
671
|
phase: "reconnecting",
|
|
654
|
-
detail:
|
|
672
|
+
detail: announcement.detail,
|
|
655
673
|
retryInSec: Math.ceil(remainingMs / 1000),
|
|
656
674
|
retryInMs: remainingMs,
|
|
657
675
|
attempt: thinkingRetries,
|
|
658
676
|
maxRetries: THINKING_RETRY_BUDGET,
|
|
659
|
-
errClass:
|
|
677
|
+
errClass: announcement.errClass,
|
|
660
678
|
}));
|
|
661
679
|
continue;
|
|
662
680
|
}
|
|
@@ -664,16 +682,16 @@ export function runStreamingBrain(args) {
|
|
|
664
682
|
throw failure.err;
|
|
665
683
|
const delayMs = retryBackoffMs(baseDelay, attempt);
|
|
666
684
|
attempt++;
|
|
667
|
-
emitBrainTelemetry({ kind: "retry", attempt, phase: "midstream", errClass:
|
|
685
|
+
emitBrainTelemetry({ kind: "retry", attempt, phase: "midstream", errClass: announcement.errClass, nextDelayMs: delayMs });
|
|
668
686
|
announcedRetry = true;
|
|
669
687
|
await sleepAnnouncingRetry(delayMs, signal, (remainingMs) => ({
|
|
670
688
|
phase: "reconnecting",
|
|
671
|
-
detail:
|
|
689
|
+
detail: announcement.detail,
|
|
672
690
|
retryInSec: Math.ceil(remainingMs / 1000),
|
|
673
691
|
retryInMs: remainingMs,
|
|
674
692
|
attempt,
|
|
675
693
|
maxRetries,
|
|
676
|
-
errClass:
|
|
694
|
+
errClass: announcement.errClass,
|
|
677
695
|
}));
|
|
678
696
|
}
|
|
679
697
|
}
|
package/dist/brain/timeout.d.ts
CHANGED
|
@@ -38,17 +38,38 @@ export declare function resolveStallTimeoutMs(value: number | undefined, knob: s
|
|
|
38
38
|
* - connect 30s: bounds the fetch→response-HEADERS wait — the one segment every post-headers
|
|
39
39
|
* watchdog is blind to. On a streaming API the model's time is spent AFTER headers, so 30s with
|
|
40
40
|
* none at all is a hung gateway, not a slow model.
|
|
41
|
-
* - first-token
|
|
41
|
+
* - first-token 600s: bounds the headers→first-delta wait, NOT the model's thought (the first
|
|
42
42
|
* THINKING delta already counts as the first token, design/17 O5 — see
|
|
43
|
-
* {@link BrainTimeoutConfig.firstTokenTimeoutMs}), so a slow reasoner is safe
|
|
44
|
-
* literally NOTHING is a hung gateway
|
|
43
|
+
* {@link BrainTimeoutConfig.firstTokenTimeoutMs}), so a slow reasoner is safe. The old 120s rested
|
|
44
|
+
* on "2 minutes with literally NOTHING is a hung gateway", which is FALSE for a real class of
|
|
45
|
+
* backend: a self-hosted server working through a long prefill emits nothing at all for 2–3
|
|
46
|
+
* minutes, and the watchdog cancelled it — burning the prefill and re-sending a request that then
|
|
47
|
+
* burned it again. A guardrail whose reason is false for healthy traffic is not a guardrail. 600s
|
|
48
|
+
* is the same order of magnitude as the reference implementation's own request timeout, and the
|
|
49
|
+
* shapes this bound exists for (a gateway that accepted the request and then died) are still
|
|
50
|
+
* caught, just later; the task's own hard abort remains the outer bound for everything else.
|
|
51
|
+
* HONEST CEILING (measured, adversarial round): this watchdog is not the only clock over a
|
|
52
|
+
* byte-silent stream, so 600s is the bound this engine ASKS FOR, not the silence a deployment is
|
|
53
|
+
* necessarily granted. On Node's own `fetch` — the default transport for both adapters, and the
|
|
54
|
+
* one a deployment gets by supplying no `fetchImpl` — undici destroys a body that has produced no
|
|
55
|
+
* bytes for 300s (`bodyTimeout`, default `300e3`), so byte-silence past 300s ends the attempt
|
|
56
|
+
* there and arrives as a read rejection reported `transport`. That report is CORRECT (the
|
|
57
|
+
* transport really did cut it) and the seat to change it exists: `fetchImpl` on both brain configs
|
|
58
|
+
* takes any `fetch`, so a deployment needing the full 600s passes one built on an undici
|
|
59
|
+
* `Agent`/`Dispatcher` with its own `bodyTimeout`. Named here so the next reader who measures ~300s
|
|
60
|
+
* of tolerance does not conclude this number is a lie.
|
|
61
|
+
* The OTHER clock that used to sit below this one is fixed rather than disclosed:
|
|
62
|
+
* {@link import("../orchestration/workflow.js").WORKFLOW_AGENT_STALL_MS} is now DERIVED from
|
|
63
|
+
* {@link STALL_CONNECT_MS} + this value, so the coarse agent-restart no longer sits UNDER a single
|
|
64
|
+
* silent provider call by construction. Move this number and that one follows. It is a floor, not
|
|
65
|
+
* a guarantee of ordering — see that constant's own comment for the cases that still invert.
|
|
45
66
|
* - idle 90s: mid-stream silence after output started — a healthy stream emits steadily
|
|
46
67
|
* (comfortably above any provider keep-alive cadence).
|
|
47
68
|
* ADVISORY: a brain whose construction-time {@link BrainTimeoutConfig} sets the corresponding field
|
|
48
69
|
* keeps it (deployment sovereignty; see the `StreamOptions.stallTimeouts` priority chain).
|
|
49
70
|
*/
|
|
50
71
|
export declare const STALL_CONNECT_MS = 30000;
|
|
51
|
-
export declare const STALL_FIRST_TOKEN_MS =
|
|
72
|
+
export declare const STALL_FIRST_TOKEN_MS = 600000;
|
|
52
73
|
export declare const STALL_IDLE_MS = 90000;
|
|
53
74
|
export interface BrainTimeoutConfig {
|
|
54
75
|
/** Abort the request if `fetch` doesn't return response headers within this many ms. Retryable. */
|
package/dist/brain/timeout.js
CHANGED
|
@@ -15,7 +15,7 @@ export function resolveStallTimeoutMs(value, knob) {
|
|
|
15
15
|
return value;
|
|
16
16
|
}
|
|
17
17
|
export const STALL_CONNECT_MS = 30_000;
|
|
18
|
-
export const STALL_FIRST_TOKEN_MS =
|
|
18
|
+
export const STALL_FIRST_TOKEN_MS = 600_000;
|
|
19
19
|
export const STALL_IDLE_MS = 90_000;
|
|
20
20
|
export function createConnectController(connectTimeoutMs, outerSignal) {
|
|
21
21
|
const ac = new AbortController();
|
|
@@ -33,9 +33,10 @@ import type { PermissionResult } from "./tool-policy.js";
|
|
|
33
33
|
*
|
|
34
34
|
* - `content_question` — the reserved question tool: a content-ROUTING signal, not a permission
|
|
35
35
|
* question; it leaves the gate through the content channel, never through a permission decider.
|
|
36
|
-
* - `
|
|
36
|
+
* - `ancestor_marked` — the call is MARKED by an ancestor (an inherited approver-unavailable float, or
|
|
37
37
|
* a durable mandate floated down because this task can park it): it must reach the park with no
|
|
38
|
-
* synchronous decision-maker in between.
|
|
38
|
+
* synchronous decision-maker in between. The word names WHO raised it (an ancestor's mark), like every
|
|
39
|
+
* other member; whether the ask can be resolved is the park's business, not the origin's.
|
|
39
40
|
* - `org_unavailable` — the deployment is org-governed and could not read its snapshot: the fail-closed
|
|
40
41
|
* tighten, cleared by a person only ("does the org forbid this?" is unanswered, and no automatic
|
|
41
42
|
* lane may answer it).
|
|
@@ -69,7 +70,7 @@ import type { PermissionResult } from "./tool-policy.js";
|
|
|
69
70
|
* observer or an approval card can say WHICH engine layer raised the question, which one word
|
|
70
71
|
* could not.
|
|
71
72
|
*/
|
|
72
|
-
export declare const ASK_ORIGINS: readonly ["content_question", "
|
|
73
|
+
export declare const ASK_ORIGINS: readonly ["content_question", "ancestor_marked", "org_unavailable", "org_rule", "rule_store_unavailable", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"];
|
|
73
74
|
export type AskOrigin = (typeof ASK_ORIGINS)[number];
|
|
74
75
|
/** Whether a value is a member of the closed origin set. Exported because the word crosses process
|
|
75
76
|
* boundaries: a host validating one it received must not hand-roll the set. */
|
|
@@ -83,6 +84,23 @@ export declare function isAskOrigin(v: unknown): v is AskOrigin;
|
|
|
83
84
|
* other direction (a row for a word that is not in the set).
|
|
84
85
|
*/
|
|
85
86
|
export type AssertAllKeysHandled<T extends never> = T;
|
|
87
|
+
/**
|
|
88
|
+
* WHICH mechanism left the persisted-rule lane unable to enforce the person's deny/ask rows for a call —
|
|
89
|
+
* the two-word set behind the one origin word `rule_store_unavailable` (the AUTHORITY semantics are
|
|
90
|
+
* identical; the mechanism differs, and a consumer that renders "the store is down" for a call the lexer
|
|
91
|
+
* could not read is telling the person to fix the wrong thing):
|
|
92
|
+
* - `store` — the wired store could not be READ for this call (a read failure, a timeout, a thrown adapter);
|
|
93
|
+
* - `call` — the store was read, but the CALL could not be read against the person's rows (the
|
|
94
|
+
* tightening lexer's `unreadable` word: an expansion where a rule reads a word, an unterminated quote,
|
|
95
|
+
* a syntax error — the ask's message carries the lexer's own sentence).
|
|
96
|
+
* The gate reads the lane's answer ONCE into {@link AskOriginFacts.ruleStore}, derives the origin from it and
|
|
97
|
+
* stamps the word onto the surviving ask (`ruleStoreUnreadable`), which the approval request and the durable
|
|
98
|
+
* row carry beside `origin`.
|
|
99
|
+
*/
|
|
100
|
+
export declare const RULE_STORE_UNREADABLE_KINDS: readonly ["store", "call"];
|
|
101
|
+
export type RuleStoreUnreadable = (typeof RULE_STORE_UNREADABLE_KINDS)[number];
|
|
102
|
+
/** Membership test for {@link RuleStoreUnreadable} — the screen a row reader applies to a stored word. */
|
|
103
|
+
export declare function isRuleStoreUnreadable(v: unknown): v is RuleStoreUnreadable;
|
|
86
104
|
/** The facts about one call that only the gate holds — the derivation's inputs beyond the decision
|
|
87
105
|
* itself. Every member is a plain boolean/word read ONCE by the gate, so the derivation is a pure
|
|
88
106
|
* function that a test can drive arm by arm. */
|
|
@@ -95,9 +113,10 @@ export interface AskOriginFacts {
|
|
|
95
113
|
* read-back off the decision (a policy can put `decisionReason: "org_rule"` on its own ask; the
|
|
96
114
|
* gate's cell cannot be written by a policy). */
|
|
97
115
|
readonly org: "rule" | "unavailable" | undefined;
|
|
98
|
-
/** The gate's own record of its persisted-rule lane's READ:
|
|
99
|
-
*
|
|
100
|
-
|
|
116
|
+
/** The gate's own record of its persisted-rule lane's READ: which {@link RuleStoreUnreadable} mechanism
|
|
117
|
+
* left the lane's deny/ask rows unenforceable for this call, or undefined (the lane read the store and
|
|
118
|
+
* the call). Written only by the gate. */
|
|
119
|
+
readonly ruleStore?: RuleStoreUnreadable | undefined;
|
|
101
120
|
/** Which of the gate's OWN post-fold tightens minted the standing ask, if one did — again the gate's
|
|
102
121
|
* record of its own layer (a tighten replaces an `allow` wholesale, so at most one fires per pass),
|
|
103
122
|
* never a read-back of `decisionReason: "safety"` off a decision a policy could have composed. */
|
|
@@ -135,7 +154,7 @@ export declare function askOriginOf(decision: Extract<PermissionResult, {
|
|
|
135
154
|
*/
|
|
136
155
|
export declare const ORIGIN_IMPLIES_REAL_APPROVAL: {
|
|
137
156
|
readonly content_question: false;
|
|
138
|
-
readonly
|
|
157
|
+
readonly ancestor_marked: false;
|
|
139
158
|
readonly org_unavailable: "at_derivation";
|
|
140
159
|
readonly org_rule: "at_derivation";
|
|
141
160
|
readonly rule_store_unavailable: "at_derivation";
|
|
@@ -156,7 +175,7 @@ export type RealApprovalTableCoversEveryOrigin = AssertAllKeysHandled<Exclude<As
|
|
|
156
175
|
* answer, each for its own reason (kept beside its row so the reason travels with the fact):
|
|
157
176
|
* - `content_question`: a permission decider's verdict is neither an answer nor a park — letting it
|
|
158
177
|
* speak would skip the durable mandate, the park and the content route in one step.
|
|
159
|
-
* - `
|
|
178
|
+
* - `ancestor_marked`: a marked call must reach the park with no synchronous decision-maker between.
|
|
160
179
|
* - `org_unavailable` / `org_rule`: judgment may clear a `requiresRealApproval` bit a POLICY minted,
|
|
161
180
|
* but not the org's — while adjudication is unavailable the org's answer is unknown, and an org
|
|
162
181
|
* ask carries the org's own "a person nods" semantic.
|
|
@@ -172,6 +191,11 @@ export type RealApprovalTableCoversEveryOrigin = AssertAllKeysHandled<Exclude<As
|
|
|
172
191
|
* deployment armed the classifier to resolve (the classify doctrine's whole point for the shell
|
|
173
192
|
* tier; the safety marks make the ask NON-BUDGETABLE at the durable park, a different axis from who
|
|
174
193
|
* may answer it live).
|
|
194
|
+
* A PROBE mandate riding the same `shell_gate_tighten` word (a read-boundary / out-of-root /
|
|
195
|
+
* recursive-read probe verdict, or a probe that did not answer) is NOT this table's to admit: the
|
|
196
|
+
* gate's `probeBoundaryMandate` excludes it at both classifier stations, the way the allow layer
|
|
197
|
+
* excludes it from a standing rule. This table says which QUESTIONS are the classifier's; the probe
|
|
198
|
+
* words say which asks are nobody's but a person's.
|
|
175
199
|
* - `policy`: YES — including a safety policy's `requiresRealApproval` ask (a live verdict is
|
|
176
200
|
* judgment, and arming auto mode is the deployment's explicit choice to let the classifier be the
|
|
177
201
|
* explicit ask-resolution for those classes).
|
|
@@ -180,7 +204,7 @@ export type RealApprovalTableCoversEveryOrigin = AssertAllKeysHandled<Exclude<As
|
|
|
180
204
|
*/
|
|
181
205
|
declare const CLASSIFIER_MAY_ANSWER: {
|
|
182
206
|
readonly content_question: false;
|
|
183
|
-
readonly
|
|
207
|
+
readonly ancestor_marked: false;
|
|
184
208
|
readonly org_unavailable: false;
|
|
185
209
|
readonly org_rule: false;
|
|
186
210
|
readonly rule_store_unavailable: false;
|
|
@@ -195,4 +219,54 @@ declare const CLASSIFIER_MAY_ANSWER: {
|
|
|
195
219
|
export type ClassifierTableCoversEveryOrigin = AssertAllKeysHandled<Exclude<AskOrigin, keyof typeof CLASSIFIER_MAY_ANSWER>>;
|
|
196
220
|
/** Whether the auto-mode classifier may resolve an ask of this origin (see the table's contract). */
|
|
197
221
|
export declare function classifierMayAnswer(origin: AskOrigin): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* The mandate provenance of one call, judged from the SAME mark inputs the gate is driven with —
|
|
224
|
+
* the single source for "could a persisted allow rule clear this ask?". Allow rules silence the
|
|
225
|
+
* classifier's questions, never a mandated one, and this predicate is the mandated-family half of
|
|
226
|
+
* that boundary (the real-approval/governance half rides the decision's own `requiresRealApproval`
|
|
227
|
+
* bit, which the org layer stamps):
|
|
228
|
+
* · `probeMandated` — this CALL's own reversibility probe declared its demotion STRUCTURAL
|
|
229
|
+
* (`"probe_mandate"`), judged FIRST because it is the only per-call member here: the three below
|
|
230
|
+
* are properties of the TOOL and are true of every call on the seat, so a mandate that is true of
|
|
231
|
+
* this one call must not be shadowed by the tier that happens to carry it (#502: the built-in
|
|
232
|
+
* shell probe raises it for a listed reader naming a path outside the session's roots — a boundary
|
|
233
|
+
* the deployment declared, which is exactly what the classify tier alone cannot say);
|
|
234
|
+
* · `egress` — the tool's own external-write mark, judged next: it is the tool's declaration even
|
|
235
|
+
* when the coarse doctrine also installed a shell tier on the same seat;
|
|
236
|
+
* · `shellGated` + tier `"always"` — the operator's per-call confirmation doctrine
|
|
237
|
+
* (`"operator_always"`); the classify doctrine installs `"maybe"`, and THOSE asks stay the rule
|
|
238
|
+
* lane's home turf (`undefined`) — that is the don't-ask-again main case, and the per-call member
|
|
239
|
+
* above is deliberately the ONLY thing that carves a mandate out of it;
|
|
240
|
+
* · a tool's OWN `"always"`/`"maybe"` irreversibility tier without the doctrine (`"tool_marks"`).
|
|
241
|
+
*
|
|
242
|
+
* Two consumers, one derivation: the gate's silencing arm (a matching rule is disclosed as shadowed
|
|
243
|
+
* instead of clearing the ask) and the runner's suggestion factory (a mandated ask offers no
|
|
244
|
+
* "stop asking me this" option — a rule minted from it would never clear it). A drift between the
|
|
245
|
+
* two would let a card offer a rule the lane then refuses to honor. The per-call member reaches both
|
|
246
|
+
* the same way every other per-call fact does: the gate stamps it on the surviving ask, the
|
|
247
|
+
* synchronous sites read it off the decision they spread, and the park leg threads its own parameter.
|
|
248
|
+
*/
|
|
249
|
+
export declare function persistedRuleMandateOf(marks: {
|
|
250
|
+
egress?: boolean;
|
|
251
|
+
shellGated?: boolean;
|
|
252
|
+
irreversibility?: "never" | "maybe" | "always";
|
|
253
|
+
/** #502: the surviving ask's engine-stamped `probeMandated` — see
|
|
254
|
+
* {@link import("./types.js").ReversibilityVerdict.mandated}. */
|
|
255
|
+
probeMandated?: boolean;
|
|
256
|
+
/** The pass's own record that the probe was consulted and did not answer (`GatePass.probeUnanswered`):
|
|
257
|
+
* the boundary is unknown, which no standing yes may stand in for. The runner's suggestion factory does
|
|
258
|
+
* not see the pass and never passes it — a card may offer a rule the lane then discloses as shadowed
|
|
259
|
+
* (loud, never a silent override), the same drift window the `matchedAskRule` conjunct already has. */
|
|
260
|
+
probeUnanswered?: boolean;
|
|
261
|
+
}): "operator_always" | "tool_marks" | "probe_mandate" | "probe_unanswered" | undefined;
|
|
262
|
+
/** The PROBE half of the mandated family (the deployment's read boundary, judged per call): neither a standing rule
|
|
263
|
+
* nor the classifier clears these two words; `tool_marks`/`operator_always` bind the rule lane only (`safety_tighten`). */
|
|
264
|
+
export declare function probeBoundaryMandate(word: ReturnType<typeof persistedRuleMandateOf>): boolean;
|
|
265
|
+
/** An ASK that carries its probe's mandate — the per-call member the gate stamped (`probeMandated`), read
|
|
266
|
+
* through the same family word so every consumer (the inherited classifier station, the two ancestor
|
|
267
|
+
* sandbox-admission arms) is one predicate, not three spellings. Adding a probe word to the family
|
|
268
|
+
* changes all of them at once. */
|
|
269
|
+
export declare function probeMandatedAsk(ask: {
|
|
270
|
+
probeMandated?: boolean;
|
|
271
|
+
}): boolean;
|
|
198
272
|
export {};
|
package/dist/core/ask-origin.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
export const ASK_ORIGINS = ["content_question", "
|
|
1
|
+
export const ASK_ORIGINS = ["content_question", "ancestor_marked", "org_unavailable", "org_rule", "rule_store_unavailable", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"];
|
|
2
2
|
const ASK_ORIGIN_SET = new Set(ASK_ORIGINS);
|
|
3
3
|
export function isAskOrigin(v) {
|
|
4
4
|
return ASK_ORIGIN_SET.has(v);
|
|
5
5
|
}
|
|
6
|
+
export const RULE_STORE_UNREADABLE_KINDS = ["store", "call"];
|
|
7
|
+
const RULE_STORE_UNREADABLE_SET = new Set(RULE_STORE_UNREADABLE_KINDS);
|
|
8
|
+
export function isRuleStoreUnreadable(v) {
|
|
9
|
+
return RULE_STORE_UNREADABLE_SET.has(v);
|
|
10
|
+
}
|
|
6
11
|
export function askOriginOf(decision, facts) {
|
|
7
12
|
const origin = originLadder(decision, facts);
|
|
8
13
|
if (ORIGIN_IMPLIES_REAL_APPROVAL[origin] === "at_derivation" && decision.requiresRealApproval !== true) {
|
|
@@ -14,12 +19,12 @@ function originLadder(decision, facts) {
|
|
|
14
19
|
if (facts.contentQuestion)
|
|
15
20
|
return "content_question";
|
|
16
21
|
if (facts.markedUnresolvable)
|
|
17
|
-
return "
|
|
22
|
+
return "ancestor_marked";
|
|
18
23
|
if (facts.org === "unavailable")
|
|
19
24
|
return "org_unavailable";
|
|
20
25
|
if (facts.org === "rule")
|
|
21
26
|
return "org_rule";
|
|
22
|
-
if (facts.ruleStore
|
|
27
|
+
if (facts.ruleStore !== undefined)
|
|
23
28
|
return "rule_store_unavailable";
|
|
24
29
|
if (decision.decisionReason === "hook")
|
|
25
30
|
return "hook";
|
|
@@ -35,7 +40,7 @@ function originLadder(decision, facts) {
|
|
|
35
40
|
}
|
|
36
41
|
export const ORIGIN_IMPLIES_REAL_APPROVAL = {
|
|
37
42
|
content_question: false,
|
|
38
|
-
|
|
43
|
+
ancestor_marked: false,
|
|
39
44
|
org_unavailable: "at_derivation",
|
|
40
45
|
org_rule: "at_derivation",
|
|
41
46
|
rule_store_unavailable: "at_derivation",
|
|
@@ -48,7 +53,7 @@ export const ORIGIN_IMPLIES_REAL_APPROVAL = {
|
|
|
48
53
|
};
|
|
49
54
|
const CLASSIFIER_MAY_ANSWER = {
|
|
50
55
|
content_question: false,
|
|
51
|
-
|
|
56
|
+
ancestor_marked: false,
|
|
52
57
|
org_unavailable: false,
|
|
53
58
|
org_rule: false,
|
|
54
59
|
rule_store_unavailable: false,
|
|
@@ -62,3 +67,24 @@ const CLASSIFIER_MAY_ANSWER = {
|
|
|
62
67
|
export function classifierMayAnswer(origin) {
|
|
63
68
|
return CLASSIFIER_MAY_ANSWER[origin];
|
|
64
69
|
}
|
|
70
|
+
export function persistedRuleMandateOf(marks) {
|
|
71
|
+
return marks.probeMandated === true
|
|
72
|
+
? "probe_mandate"
|
|
73
|
+
: marks.probeUnanswered === true
|
|
74
|
+
? "probe_unanswered"
|
|
75
|
+
: marks.egress === true
|
|
76
|
+
? "tool_marks"
|
|
77
|
+
: marks.shellGated === true
|
|
78
|
+
? marks.irreversibility === "always"
|
|
79
|
+
? "operator_always"
|
|
80
|
+
: undefined
|
|
81
|
+
: marks.irreversibility === "always" || marks.irreversibility === "maybe"
|
|
82
|
+
? "tool_marks"
|
|
83
|
+
: undefined;
|
|
84
|
+
}
|
|
85
|
+
export function probeBoundaryMandate(word) {
|
|
86
|
+
return word === "probe_mandate" || word === "probe_unanswered";
|
|
87
|
+
}
|
|
88
|
+
export function probeMandatedAsk(ask) {
|
|
89
|
+
return probeBoundaryMandate(persistedRuleMandateOf({ probeMandated: ask.probeMandated === true }));
|
|
90
|
+
}
|
package/dist/core/auto-mode.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ToolCallRequest } from "./tool-policy.js";
|
|
2
2
|
/**
|
|
3
|
-
* WHY an `unavailable` verdict could not run — the closed set behind the verdict arm, the
|
|
4
|
-
*
|
|
5
|
-
* the
|
|
3
|
+
* WHY an `unavailable` verdict could not run — the closed set behind the verdict arm, the model-facing
|
|
4
|
+
* sentence's parenthetical and the `auto_mode.classified` trace frame's `cause`, so the three spell one
|
|
5
|
+
* word (the deny's structural face carries only the round's FORM, `GateDisposition.cause`). `error` = the model leg
|
|
6
6
|
* threw or rejected (a route failure at classify time reads here too — the derived-route pre-flight fell
|
|
7
7
|
* back BEFORE any decide, so there is no separate word for it); `timeout` = the round-trip cap.
|
|
8
8
|
*/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The checkpoint SEAT a run hands its children — the projection of `resolveCheckpointStore(spec, deps)` (the store the
|
|
3
|
+
* run parks in) plus the caller's own off switch onto the one value both delegation lanes copy verbatim onto a child
|
|
4
|
+
* spec (`ToolExecuteContext.checkpointStoreForChildren` on the Task lane, `RunWorkflowToolDeps.parentCheckpointStore`
|
|
5
|
+
* on the workflow lane). Minted once per prepare by the driver; read by the lanes and by the plan-pair face.
|
|
6
|
+
*/
|
|
7
|
+
import type { CheckpointStore } from "./checkpoint-store.js";
|
|
8
|
+
/** The seat a run hands its children: the store it resolved, or the word `"disabled"` when the caller disarmed the run. */
|
|
9
|
+
export type CheckpointSeat = CheckpointStore | "disabled";
|
|
10
|
+
/** Project a run's spec + resolved store onto the seat its children inherit: `"disabled"` when the caller disarmed the
|
|
11
|
+
* run (a removal that presses on the whole subtree), else the resolved store, else NOTHING — an unset spec on a
|
|
12
|
+
* deployment without a store hands down no seat, and the child's own door resolves its runner's deployment (a split
|
|
13
|
+
* child-execution runner may carry a store the host has none of; that topology is the deployment's own author). */
|
|
14
|
+
export declare function checkpointSeatOf(spec: {
|
|
15
|
+
checkpointStore?: CheckpointStore | "disabled";
|
|
16
|
+
}, resolved: CheckpointStore | undefined): CheckpointSeat | undefined;
|
|
17
|
+
/** The store a seat names — none for `"disabled"` and for no seat. */
|
|
18
|
+
export declare function checkpointStoreOfSeat(seat: CheckpointSeat | undefined): CheckpointStore | undefined;
|
|
@@ -444,14 +444,14 @@ export declare function buildRiskDescriptor(input: {
|
|
|
444
444
|
export interface RealApprovalGateBit {
|
|
445
445
|
/** The parked ask's ORIGIN word — a projection of the same closed set the card, the observer and the
|
|
446
446
|
* `tool_end` frame carry ({@link import("./ask-origin.js").AskOrigin}), never a second vocabulary. It
|
|
447
|
-
* is the ask's ROUTING word: a marked call reads `
|
|
447
|
+
* is the ask's ROUTING word: a marked call reads `ancestor_marked` even when the org governs it, because
|
|
448
448
|
* the ladder routes a marked call to the park before anything else. Rows minted before the projection
|
|
449
449
|
* carried a three-word subset of this set (`org_rule` / `org_unavailable` / `policy`), so every stored
|
|
450
450
|
* value is still a member. */
|
|
451
451
|
origin: import("./ask-origin.js").AskOrigin;
|
|
452
452
|
/** The ORG FACT the ask was minted under (`"rule"` = an org rule asked, `"unavailable"` = org adjudication
|
|
453
453
|
* unreachable), kept beside the routing word because routing can outrank the word (a marked call reads
|
|
454
|
-
* `
|
|
454
|
+
* `ancestor_marked`) while the governance stays. The #120 governed mark and the resume belts read THIS
|
|
455
455
|
* member through {@link realApprovalOrgFact} (absent on rows minted before it: the org words imply it). */
|
|
456
456
|
org?: "rule" | "unavailable";
|
|
457
457
|
}
|
|
@@ -813,14 +813,10 @@ export type PendingAction = {
|
|
|
813
813
|
* Written only when it is a member of the set; a pre-existing row reads absent (unreported).
|
|
814
814
|
* Display metadata — the resume belts keep reading the gate's own bit. */
|
|
815
815
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
816
|
-
/** #
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
|
|
820
|
-
* the classifier was unavailable" sentence) — the resume belts never read it. */
|
|
821
|
-
classifierUnavailable?: {
|
|
822
|
-
readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
|
|
823
|
-
};
|
|
816
|
+
/** #688 C3 (additive, same precedent as `origin`) — the mechanism behind an `origin: "rule_store_unavailable"`
|
|
817
|
+
* (`store` / `call`, {@link import("./ask-origin.js").RULE_STORE_UNREADABLE_KINDS}). Written only when it is a
|
|
818
|
+
* member of the set; a pre-existing row reads absent. Display metadata for the durable card. */
|
|
819
|
+
ruleStoreUnreadable?: import("./ask-origin.js").RuleStoreUnreadable;
|
|
824
820
|
/**
|
|
825
821
|
* design/80 D-1 §2 (slice 1a.2): the server-minted **opaque** boundInputHash of {@link args} — a
|
|
826
822
|
* SHA-256 (hex) via {@link import("./canonical-json.js").boundInputHashOf}, computed ONCE here at
|
|
@@ -1926,12 +1922,9 @@ export interface CheckpointSummary {
|
|
|
1926
1922
|
* value is a member of the closed set ({@link import("./ask-origin.js").isAskOrigin}) — a bad value
|
|
1927
1923
|
* reads as absent, never as a word. OMITTED when the row carries none (a pre-existing row). */
|
|
1928
1924
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
1929
|
-
/** The row's `PendingAction.tool_approval.
|
|
1930
|
-
*
|
|
1931
|
-
|
|
1932
|
-
classifierUnavailable?: {
|
|
1933
|
-
readonly cause: import("./auto-mode.js").AutoModeUnavailableCause;
|
|
1934
|
-
};
|
|
1925
|
+
/** The row's `PendingAction.tool_approval.ruleStoreUnreadable` (#688 C3), echoed ONLY when the stored word is a
|
|
1926
|
+
* member of the closed set ({@link import("./ask-origin.js").isRuleStoreUnreadable}); OMITTED otherwise. */
|
|
1927
|
+
ruleStoreUnreadable?: import("./ask-origin.js").RuleStoreUnreadable;
|
|
1935
1928
|
/**
|
|
1936
1929
|
* #438: PRESENCE ONLY — this pending `tool_approval` row's payload carries at least one DIRECTIONAL
|
|
1937
1930
|
* format control (the {@link import("./tool-policy.js").AskRequest.hasBidiControls} character class;
|
|
@@ -2,8 +2,8 @@ import { randomBytes, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { uuidv7 } from "../internal/harness.js";
|
|
3
3
|
import { PROBE_CAUSE_PATH_MAX, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
4
4
|
import { carriesBidiControls } from "./tool-policy.js";
|
|
5
|
-
import {
|
|
6
|
-
import { isAskOrigin } from "./ask-origin.js";
|
|
5
|
+
import { readDenialLimitFallback } from "./auto-mode.js";
|
|
6
|
+
import { isAskOrigin, isRuleStoreUnreadable } from "./ask-origin.js";
|
|
7
7
|
import { checkpointExecutionRecorded, executionRecordDisposition, ExecutionOutcomeConflictError, } from "./checkpoint-execution-record.js";
|
|
8
8
|
import { renderUntrustedCommandText, stripFormatCharacters } from "./permission-rule-model.js";
|
|
9
9
|
import { redactSecrets } from "./untrusted-egress.js";
|
|
@@ -277,8 +277,11 @@ export function summarizeCheckpoint(cp) {
|
|
|
277
277
|
const fb = readDenialLimitFallback(tool?.denialLimitFallback);
|
|
278
278
|
return fb !== undefined ? { denialLimitFallback: fb } : {};
|
|
279
279
|
})(),
|
|
280
|
-
...(
|
|
281
|
-
|
|
280
|
+
...(() => {
|
|
281
|
+
const origin = tool?.origin;
|
|
282
|
+
const ruleStoreUnreadable = tool?.ruleStoreUnreadable;
|
|
283
|
+
return { ...(isAskOrigin(origin) ? { origin } : {}), ...(isRuleStoreUnreadable(ruleStoreUnreadable) ? { ruleStoreUnreadable } : {}) };
|
|
284
|
+
})(),
|
|
282
285
|
...(hasBidi ? { hasBidiControls: true } : {}),
|
|
283
286
|
};
|
|
284
287
|
}
|
|
@@ -235,6 +235,24 @@ export interface EngineNotice {
|
|
|
235
235
|
* (whoever answered the card is the one entitled to hear the answer ran nothing); a host may
|
|
236
236
|
* forward it on its own wire.
|
|
237
237
|
*
|
|
238
|
+
* - `"task.interrupt_unconsumed"` — a HARD stop ({@link TaskStream.interrupt}, the task's `signal`,
|
|
239
|
+
* the walltime or the turns limit) landed AFTER the run's last turn had already given its answer
|
|
240
|
+
* (`stopReason:"stop"`, not partial, and no steer / follow-up of the person's still pending — a
|
|
241
|
+
* queued continuation makes that "stop" work in progress, and a stop that cuts it stays the
|
|
242
|
+
* ordinary abort with its undrained-input account) and before the loop's end — what it aborted was the run's
|
|
243
|
+
* tail (a final-boundary compaction is mooted, a stop hook's wait is cut), never its work. The
|
|
244
|
+
* answer stands: the run ends `completed` with it as `result`, no `[Request interrupted by user]`
|
|
245
|
+
* marker is written and no `limits.*` terminal is minted for the axis that fired; this notice is
|
|
246
|
+
* the stop's only trace. Minted once per such run, after the terminal arbitration (so it never
|
|
247
|
+
* claims a completed ending the terminal did not give — a budget ceiling crossed by the last
|
|
248
|
+
* turn's own spend still fails the run and mints nothing here); `detail: { sessionId, runId,
|
|
249
|
+
* taskId?, origin }` with `origin` ∈ `"interrupt"` (the verb) | `"walltime"` | `"turns"` |
|
|
250
|
+
* `"signal"` (every other run-level abort: the task's own `signal`, a parent's cancel, a
|
|
251
|
+
* `destroy()`, a refused preempt — the leg names the three it can tell apart and files the rest
|
|
252
|
+
* here). Audience `"user"`. A stop that arrives
|
|
253
|
+
* AFTER the loop's end is refused typed (`steering.not_running`, every verb alike) and mints
|
|
254
|
+
* nothing: the refusal itself is the loud answer.
|
|
255
|
+
*
|
|
238
256
|
* - `"checkpoint.execution_outcome_unrecorded"` — the resumed leg's pending-call resolver disposed
|
|
239
257
|
* the decided action (executed it, delivered the refusal, or vetoed the person's yes) and filed
|
|
240
258
|
* the frame's record through `CheckpointStore.recordExecutionOutcome`, and the store answered
|
|
@@ -412,7 +430,9 @@ export interface EngineNotice {
|
|
|
412
430
|
* consolidation run, so the CLEAN arm's authoring prompts were composed WITHOUT the marked
|
|
413
431
|
* rows: anything this run's clean products say was written without them, and their silence on a
|
|
414
432
|
* topic therefore does not mean the library is silent on it (the withheld rows still stand and
|
|
415
|
-
* are still readable).
|
|
433
|
+
* are still readable). OPERATOR audience (#692 C-a): the run happens outside any session, so
|
|
434
|
+
* there is no `sessionId` to route a user row to — the receipt's `withheldFromCleanArm` /
|
|
435
|
+
* `withheldNeverSent` are the reader-facing half. TWO counts, deliberately separate sentences and separate keys —
|
|
416
436
|
* `withheld` rows were kept out of the clean arm's prompts, and `neverSent` of those were not
|
|
417
437
|
* sent to the consolidation model AT ALL (a lone marked row has no group to fold, so it is
|
|
418
438
|
* withheld from every call). "Withheld from the clean arm" and "never left this machine" are
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Say a knob-resolution fact once per process.
|
|
3
|
+
*
|
|
4
|
+
* Swallow-guarded, the same posture as every other advisory sink here (`deliverEngineNotice`,
|
|
5
|
+
* `emitBrainStatus`, `emitBrainTelemetry`): an announcement must never become a failure. The write is
|
|
6
|
+
* not to a channel this process owns — a host is free to replace `console.warn` with its own logger,
|
|
7
|
+
* and a `Console` built with `ignoreErrors:false` throws on a full or closed stream. These resolvers
|
|
8
|
+
* sit on hot paths that have already done irreversible work (the shell's output clip runs AFTER the
|
|
9
|
+
* command ran), so an unguarded write could destroy a completed command's output and invite the caller
|
|
10
|
+
* to run it again. The line is ledgered BEFORE delivery so a throwing sink cannot turn one fact into a
|
|
11
|
+
* warning per call either.
|
|
12
|
+
*/
|
|
13
|
+
export declare function announceEnvKnob(line: string): void;
|
|
14
|
+
/** Test seam: the announcement de-dupes per process, so a test asserting the line must be able to clear
|
|
15
|
+
* the ledger. Never called by production code. */
|
|
16
|
+
export declare function __resetEnvKnobAnnouncements(): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const announcedEnvKnobLines = new Set();
|
|
2
|
+
export function announceEnvKnob(line) {
|
|
3
|
+
if (announcedEnvKnobLines.has(line))
|
|
4
|
+
return;
|
|
5
|
+
announcedEnvKnobLines.add(line);
|
|
6
|
+
try {
|
|
7
|
+
console.warn(line);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function __resetEnvKnobAnnouncements() {
|
|
13
|
+
announcedEnvKnobLines.clear();
|
|
14
|
+
}
|
package/dist/core/gate-fold.js
CHANGED
|
@@ -36,6 +36,8 @@ export async function runGateFold(pass) {
|
|
|
36
36
|
const refusedBy = ledger.fromDecision(pass.decision);
|
|
37
37
|
if (refusedBy !== undefined)
|
|
38
38
|
pass.deniedBy = refusedBy;
|
|
39
|
+
if (refusedBy !== undefined)
|
|
40
|
+
pass.denyCause = ledger.causeOf(pass.decision);
|
|
39
41
|
}
|
|
40
42
|
if ((pass.decision.action === "allow" || pass.decision.action === "ask") && pass.decision.updatedInput !== undefined) {
|
|
41
43
|
pass.policyRewrite = pass.decision.updatedInput;
|