@sema-agent/core 7.34.0 → 7.35.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 +156 -3
- package/dist/brain/errors.d.ts +23 -1
- package/dist/brain/errors.js +19 -2
- package/dist/brain/status-sink.d.ts +7 -1
- package/dist/brain/stream-shared.d.ts +3 -1
- package/dist/brain/stream-shared.js +16 -4
- package/dist/core/ask-origin.d.ts +34 -6
- package/dist/core/ask-origin.js +24 -23
- package/dist/core/ask-question.js +2 -1
- package/dist/core/checkpoint-store.d.ts +4 -0
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +5 -4
- package/dist/core/governance-codes.d.ts +17 -1
- package/dist/core/governance-codes.js +11 -1
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/message-utils.d.ts +2 -1
- package/dist/core/message-utils.js +2 -1
- package/dist/core/mode-policy.d.ts +3 -0
- package/dist/core/mode-policy.js +5 -0
- package/dist/core/permission-rule-model.d.ts +4 -0
- package/dist/core/permission-rule-model.js +4 -0
- package/dist/core/permission-rules.js +21 -1
- package/dist/core/persisted-rule-arms.d.ts +4 -1
- package/dist/core/persisted-rule-arms.js +4 -0
- package/dist/core/retired-keys.js +1 -1
- package/dist/core/runner/active-skill-scope.js +5 -4
- package/dist/core/runner/assemble-result.js +2 -2
- package/dist/core/runner/contracts.d.ts +26 -4
- package/dist/core/runner/denial-limit-arms.d.ts +6 -1
- package/dist/core/runner/denial-limit-arms.js +4 -4
- package/dist/core/runner/file-history-request.d.ts +26 -0
- package/dist/core/runner/file-history-request.js +90 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -1
- package/dist/core/runner/permission-rule-lanes.js +14 -11
- package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
- package/dist/core/runner/prepare-ask-lane.js +2 -2
- package/dist/core/runner/prepare-file-history.d.ts +9 -3
- package/dist/core/runner/prepare-file-history.js +4 -90
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -3
- package/dist/core/runner/prepare-gate-stations.js +15 -11
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +10 -4
- package/dist/core/runner/prepare-park-ask.d.ts +4 -2
- package/dist/core/runner/prepare-park-ask.js +5 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +3 -2
- package/dist/core/runner/prepare-policy-chain.js +171 -175
- package/dist/core/runner/prepare-task.d.ts +3 -1
- package/dist/core/runner/prepare-task.js +9 -5
- package/dist/core/runner/run-brain-sinks.js +3 -1
- package/dist/core/runner/run-leg.js +5 -3
- package/dist/core/runner/run-notification-lane.d.ts +5 -0
- package/dist/core/runner/run-notification-lane.js +3 -2
- package/dist/core/runner/runtask.d.ts +3 -1
- package/dist/core/runner/runtask.js +20 -16
- package/dist/core/runner/session-mode-seats.d.ts +48 -0
- package/dist/core/runner/session-mode-seats.js +73 -0
- package/dist/core/runner/session-rule-policy.js +3 -2
- package/dist/core/secret-env.d.ts +30 -58
- package/dist/core/secret-env.js +149 -586
- package/dist/core/sensitive-path-policy.js +2 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
- package/dist/core/tool-policy.d.ts +31 -6
- package/dist/core/tool-policy.js +35 -16
- package/dist/core/tool-registry.d.ts +36 -0
- package/dist/core/tool-registry.js +22 -1
- package/dist/core/tool-roster.d.ts +17 -2
- package/dist/core/tool-roster.js +11 -1
- package/dist/core/trace.d.ts +11 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +3 -1
- package/dist/engine/execution-env/node-execution-env.js +5 -2
- package/dist/engine/harness/agent-harness.js +2 -1
- package/dist/engine/lsp/node-lsp-manager.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/stores/file/index.d.ts +5 -3
- package/dist/stores/file/session-store.d.ts +8 -2
- package/dist/stores/file/session-store.js +5 -5
- package/dist/tools/fs/file-read-check.d.ts +2 -1
- package/dist/tools/fs/file-read-check.js +15 -7
- package/dist/tools/fs/shell-read-check.d.ts +10 -2
- package/dist/tools/fs/shell-read-check.js +12 -8
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +15 -1
- package/wire-changes/core@7.35.0.json +584 -0
- package/wire-changes/core@7.35.1.json +59 -0
|
@@ -443,6 +443,10 @@ export interface ModeArbitrationRow {
|
|
|
443
443
|
readonly sandboxAdmission: boolean;
|
|
444
444
|
/** A persisted allow rule may clear it. */
|
|
445
445
|
readonly ruleMayClear: boolean;
|
|
446
|
+
/** The reference implementation's rule / safety check answers `ask` for it, so a hook's ask beside it is not the hook's
|
|
447
|
+
* forced decision: the call runs the full permission pipeline (the reference's `hookAskFloor`) — under `dontAsk`, a
|
|
448
|
+
* refusal rather than a person's question. */
|
|
449
|
+
readonly hookAskFloor: boolean;
|
|
446
450
|
}
|
|
447
451
|
/**
|
|
448
452
|
* The mode × class table. `bypassImmune` is the one new column (nothing said it before); the classifier and
|
|
@@ -451,7 +455,9 @@ export interface ModeArbitrationRow {
|
|
|
451
455
|
* `ruleMayClear` restates {@link ruleClearanceOf}'s conclusion per class (a real-approval bit is never rule-clearable).
|
|
452
456
|
* `mode` is the permission mode's OWN question (`plan`'s two sentences, `decisionReason: "mode"`): the reference
|
|
453
457
|
* implementation puts it to a person past every allow rule and never to an automatic lane, and its bypass conversion
|
|
454
|
-
* releases it like any other non-immune ask.
|
|
458
|
+
* releases it like any other non-immune ask. `hookAskFloor` marks the classes the reference's rule / safety check answers
|
|
459
|
+
* `ask` for — an ask rule, a question the tool needs a person for, a dangerous removal, a protected or unresolvable write
|
|
460
|
+
* target, the session transcript, another's shared artifact.
|
|
455
461
|
*/
|
|
456
462
|
export declare const MODE_ARBITRATION: {
|
|
457
463
|
readonly content_question: {
|
|
@@ -459,120 +465,140 @@ export declare const MODE_ARBITRATION: {
|
|
|
459
465
|
readonly classifierEligible: false;
|
|
460
466
|
readonly sandboxAdmission: false;
|
|
461
467
|
readonly ruleMayClear: false;
|
|
468
|
+
readonly hookAskFloor: true;
|
|
462
469
|
};
|
|
463
470
|
readonly hook: {
|
|
464
471
|
readonly bypassImmune: true;
|
|
465
472
|
readonly classifierEligible: false;
|
|
466
473
|
readonly sandboxAdmission: false;
|
|
467
474
|
readonly ruleMayClear: false;
|
|
475
|
+
readonly hookAskFloor: false;
|
|
468
476
|
};
|
|
469
477
|
readonly ancestor: {
|
|
470
478
|
readonly bypassImmune: true;
|
|
471
479
|
readonly classifierEligible: false;
|
|
472
480
|
readonly sandboxAdmission: false;
|
|
473
481
|
readonly ruleMayClear: false;
|
|
482
|
+
readonly hookAskFloor: false;
|
|
474
483
|
};
|
|
475
484
|
readonly unverifiable_delete: {
|
|
476
485
|
readonly bypassImmune: true;
|
|
477
486
|
readonly classifierEligible: true;
|
|
478
487
|
readonly sandboxAdmission: false;
|
|
479
488
|
readonly ruleMayClear: false;
|
|
489
|
+
readonly hookAskFloor: true;
|
|
480
490
|
};
|
|
481
491
|
readonly transcript_write: {
|
|
482
492
|
readonly bypassImmune: false;
|
|
483
493
|
readonly classifierEligible: true;
|
|
484
494
|
readonly sandboxAdmission: false;
|
|
485
495
|
readonly ruleMayClear: false;
|
|
496
|
+
readonly hookAskFloor: true;
|
|
486
497
|
};
|
|
487
498
|
readonly org_rule: {
|
|
488
499
|
readonly bypassImmune: true;
|
|
489
500
|
readonly classifierEligible: false;
|
|
490
501
|
readonly sandboxAdmission: false;
|
|
491
502
|
readonly ruleMayClear: false;
|
|
503
|
+
readonly hookAskFloor: false;
|
|
492
504
|
};
|
|
493
505
|
readonly governance_unavailable: {
|
|
494
506
|
readonly bypassImmune: true;
|
|
495
507
|
readonly classifierEligible: false;
|
|
496
508
|
readonly sandboxAdmission: false;
|
|
497
509
|
readonly ruleMayClear: false;
|
|
510
|
+
readonly hookAskFloor: false;
|
|
498
511
|
};
|
|
499
512
|
readonly rule_unreadable: {
|
|
500
513
|
readonly bypassImmune: false;
|
|
501
514
|
readonly classifierEligible: false;
|
|
502
515
|
readonly sandboxAdmission: false;
|
|
503
516
|
readonly ruleMayClear: false;
|
|
517
|
+
readonly hookAskFloor: false;
|
|
504
518
|
};
|
|
505
519
|
readonly ask_rule: {
|
|
506
520
|
readonly bypassImmune: true;
|
|
507
521
|
readonly classifierEligible: false;
|
|
508
522
|
readonly sandboxAdmission: false;
|
|
509
523
|
readonly ruleMayClear: false;
|
|
524
|
+
readonly hookAskFloor: true;
|
|
510
525
|
};
|
|
511
526
|
readonly denial_limit_fallback: {
|
|
512
527
|
readonly bypassImmune: true;
|
|
513
528
|
readonly classifierEligible: false;
|
|
514
529
|
readonly sandboxAdmission: false;
|
|
515
530
|
readonly ruleMayClear: false;
|
|
531
|
+
readonly hookAskFloor: false;
|
|
516
532
|
};
|
|
517
533
|
readonly operator_forced: {
|
|
518
534
|
readonly bypassImmune: true;
|
|
519
535
|
readonly classifierEligible: true;
|
|
520
536
|
readonly sandboxAdmission: true;
|
|
521
537
|
readonly ruleMayClear: false;
|
|
538
|
+
readonly hookAskFloor: false;
|
|
522
539
|
};
|
|
523
540
|
readonly write_protection_unresolved: {
|
|
524
541
|
readonly bypassImmune: true;
|
|
525
542
|
readonly classifierEligible: false;
|
|
526
543
|
readonly sandboxAdmission: true;
|
|
527
544
|
readonly ruleMayClear: false;
|
|
545
|
+
readonly hookAskFloor: true;
|
|
528
546
|
};
|
|
529
547
|
readonly probe: {
|
|
530
548
|
readonly bypassImmune: false;
|
|
531
549
|
readonly classifierEligible: true;
|
|
532
550
|
readonly sandboxAdmission: false;
|
|
533
551
|
readonly ruleMayClear: false;
|
|
552
|
+
readonly hookAskFloor: false;
|
|
534
553
|
};
|
|
535
554
|
readonly write_protection: {
|
|
536
555
|
readonly bypassImmune: false;
|
|
537
556
|
readonly classifierEligible: true;
|
|
538
557
|
readonly sandboxAdmission: true;
|
|
539
558
|
readonly ruleMayClear: false;
|
|
559
|
+
readonly hookAskFloor: true;
|
|
540
560
|
};
|
|
541
561
|
readonly tool_marks_mcp: {
|
|
542
562
|
readonly bypassImmune: false;
|
|
543
563
|
readonly classifierEligible: true;
|
|
544
564
|
readonly sandboxAdmission: true;
|
|
545
565
|
readonly ruleMayClear: false;
|
|
566
|
+
readonly hookAskFloor: false;
|
|
546
567
|
};
|
|
547
568
|
readonly tool_marks_a2a: {
|
|
548
569
|
readonly bypassImmune: false;
|
|
549
570
|
readonly classifierEligible: true;
|
|
550
571
|
readonly sandboxAdmission: true;
|
|
551
572
|
readonly ruleMayClear: false;
|
|
573
|
+
readonly hookAskFloor: false;
|
|
552
574
|
};
|
|
553
575
|
readonly policy_rra: {
|
|
554
576
|
readonly bypassImmune: true;
|
|
555
577
|
readonly classifierEligible: true;
|
|
556
578
|
readonly sandboxAdmission: false;
|
|
557
579
|
readonly ruleMayClear: false;
|
|
580
|
+
readonly hookAskFloor: false;
|
|
558
581
|
};
|
|
559
582
|
readonly artifact_shared_read: {
|
|
560
583
|
readonly bypassImmune: false;
|
|
561
584
|
readonly classifierEligible: false;
|
|
562
585
|
readonly sandboxAdmission: false;
|
|
563
586
|
readonly ruleMayClear: false;
|
|
587
|
+
readonly hookAskFloor: true;
|
|
564
588
|
};
|
|
565
589
|
readonly mode: {
|
|
566
590
|
readonly bypassImmune: false;
|
|
567
591
|
readonly classifierEligible: false;
|
|
568
592
|
readonly sandboxAdmission: false;
|
|
569
593
|
readonly ruleMayClear: false;
|
|
594
|
+
readonly hookAskFloor: false;
|
|
570
595
|
};
|
|
571
596
|
readonly ordinary: {
|
|
572
597
|
readonly bypassImmune: false;
|
|
573
598
|
readonly classifierEligible: true;
|
|
574
599
|
readonly sandboxAdmission: true;
|
|
575
600
|
readonly ruleMayClear: true;
|
|
601
|
+
readonly hookAskFloor: false;
|
|
576
602
|
};
|
|
577
603
|
};
|
|
578
604
|
/** The fence over the arbitration table: `never` while every class has a row. */
|
|
@@ -613,8 +639,9 @@ export interface AskClassification {
|
|
|
613
639
|
export declare function decisionAskClassesOf(d: Extract<PermissionResult, {
|
|
614
640
|
action: "ask";
|
|
615
641
|
}>): Set<AskClass>;
|
|
616
|
-
/** Fold a class set onto one row: `bypassImmune` by OR (any immune member keeps the ask
|
|
617
|
-
* switches by AND (one refusing member withholds the lane). An empty
|
|
642
|
+
/** Fold a class set onto one row: `bypassImmune` and `hookAskFloor` by OR (any immune member keeps the ask; any floored
|
|
643
|
+
* member floors a hook's ask), the three permissive switches by AND (one refusing member withholds the lane). An empty
|
|
644
|
+
* set is `ordinary`. */
|
|
618
645
|
export declare function foldArbitrationRow(classes: ReadonlySet<AskClass>): ModeArbitrationRow;
|
|
619
646
|
/**
|
|
620
647
|
* The ONE class derivation: the decision half ({@link decisionAskClassesOf}) joined with the gate's own facts —
|
|
@@ -680,9 +707,10 @@ export type Arbitration = {
|
|
|
680
707
|
classes: readonly AskClass[];
|
|
681
708
|
};
|
|
682
709
|
/**
|
|
683
|
-
* The ONE mode read point. `dontAsk` refuses every ask but
|
|
684
|
-
*
|
|
685
|
-
*
|
|
710
|
+
* The ONE mode read point. `dontAsk` refuses every ask but an operator authority's (an org rule, an operator's forced
|
|
711
|
+
* confirmation, unreadable governance — a mode stance may not settle an operator-mandated gate) and a hook's that no
|
|
712
|
+
* member of the set floors (`hookAskFloor`: a hook's ask beside a rule's or a safety check's ask is refused, as the
|
|
713
|
+
* reference's full pipeline refuses it); `bypassPermissions` refuses a write whose protection could not be judged (a person cannot be asked, and
|
|
686
714
|
* releasing would be looser than the allow the judge already refuses), keeps every immune class and releases the
|
|
687
715
|
* rest; `acceptEdits` answers an ORDINARY ask itself on a call the caller vouches is one of the mode's file commands
|
|
688
716
|
* (`call.fileCommand`, `accepted`); `plan`, `default` and `auto` hand every ask on (plan's own questions were raised by
|
package/dist/core/ask-origin.js
CHANGED
|
@@ -192,26 +192,26 @@ export function isAskClass(v) {
|
|
|
192
192
|
return ASK_CLASS_SET.has(v);
|
|
193
193
|
}
|
|
194
194
|
export const MODE_ARBITRATION = {
|
|
195
|
-
content_question: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
196
|
-
hook: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
197
|
-
ancestor: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
198
|
-
unverifiable_delete: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
|
|
199
|
-
transcript_write: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
|
|
200
|
-
org_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
201
|
-
governance_unavailable: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
202
|
-
rule_unreadable: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
203
|
-
ask_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
204
|
-
denial_limit_fallback: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
205
|
-
operator_forced: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
|
|
206
|
-
write_protection_unresolved: { bypassImmune: true, classifierEligible: false, sandboxAdmission: true, ruleMayClear: false },
|
|
207
|
-
probe: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
|
|
208
|
-
write_protection: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
|
|
209
|
-
tool_marks_mcp: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
|
|
210
|
-
tool_marks_a2a: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
|
|
211
|
-
policy_rra: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
|
|
212
|
-
artifact_shared_read: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
213
|
-
mode: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
214
|
-
ordinary: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: true },
|
|
195
|
+
content_question: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
196
|
+
hook: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
197
|
+
ancestor: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
198
|
+
unverifiable_delete: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
199
|
+
transcript_write: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
200
|
+
org_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
201
|
+
governance_unavailable: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
202
|
+
rule_unreadable: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
203
|
+
ask_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
204
|
+
denial_limit_fallback: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
205
|
+
operator_forced: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
206
|
+
write_protection_unresolved: { bypassImmune: true, classifierEligible: false, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: true },
|
|
207
|
+
probe: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
208
|
+
write_protection: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: true },
|
|
209
|
+
tool_marks_mcp: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
210
|
+
tool_marks_a2a: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
211
|
+
policy_rra: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
212
|
+
artifact_shared_read: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
213
|
+
mode: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
214
|
+
ordinary: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: true, hookAskFloor: false },
|
|
215
215
|
};
|
|
216
216
|
export const RRA_EXPLAINERS = new Set(["unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "denial_limit_fallback", "ancestor", "artifact_shared_read"]);
|
|
217
217
|
export const RELEASABLE_ASK_CLASSES = ASK_CLASSES.filter((c) => !MODE_ARBITRATION[c].bypassImmune);
|
|
@@ -248,15 +248,16 @@ export function decisionAskClassesOf(d) {
|
|
|
248
248
|
export function foldArbitrationRow(classes) {
|
|
249
249
|
if (classes.size === 0)
|
|
250
250
|
return MODE_ARBITRATION.ordinary;
|
|
251
|
-
let bypassImmune = false, classifierEligible = true, sandboxAdmission = true, ruleMayClear = true;
|
|
251
|
+
let bypassImmune = false, classifierEligible = true, sandboxAdmission = true, ruleMayClear = true, hookAskFloor = false;
|
|
252
252
|
for (const c of classes) {
|
|
253
253
|
const row = MODE_ARBITRATION[c];
|
|
254
254
|
bypassImmune ||= row.bypassImmune;
|
|
255
255
|
classifierEligible &&= row.classifierEligible;
|
|
256
256
|
sandboxAdmission &&= row.sandboxAdmission;
|
|
257
257
|
ruleMayClear &&= row.ruleMayClear;
|
|
258
|
+
hookAskFloor ||= row.hookAskFloor;
|
|
258
259
|
}
|
|
259
|
-
return { bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear };
|
|
260
|
+
return { bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear, hookAskFloor };
|
|
260
261
|
}
|
|
261
262
|
export function classifyAsk(d, facts) {
|
|
262
263
|
const classes = decisionAskClassesOf({ ...d, foldedAskClasses: undefined });
|
|
@@ -332,7 +333,7 @@ export function arbitrateByMode(mode, c, disclosure, call) {
|
|
|
332
333
|
const classes = ASK_CLASSES.filter((k) => c.classes.has(k));
|
|
333
334
|
switch (mode) {
|
|
334
335
|
case "dontAsk":
|
|
335
|
-
return has("hook") || has("org_rule") || has("operator_forced") || has("governance_unavailable") ? { kind: "ask" } : { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
336
|
+
return (has("hook") && !c.row.hookAskFloor) || has("org_rule") || has("operator_forced") || has("governance_unavailable") ? { kind: "ask" } : { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
336
337
|
case "bypassPermissions":
|
|
337
338
|
if (has("write_protection_unresolved"))
|
|
338
339
|
return { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
@@ -5,6 +5,7 @@ import { defineTool, errorResult } from "./tools.js";
|
|
|
5
5
|
import { delimitUntrusted, inlineUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { toolFace } from "./tool-catalog-entries.js";
|
|
7
7
|
import { thrownText } from "./thrown-value.js";
|
|
8
|
+
import { callAnswersTo } from "./tool-roster.js";
|
|
8
9
|
export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
|
|
9
10
|
export function classifyQuestionOutcome(outcome) {
|
|
10
11
|
let unavailable;
|
|
@@ -134,7 +135,7 @@ export function validateAskQuestions(questions) {
|
|
|
134
135
|
export function createDurableQuestionPolicy() {
|
|
135
136
|
return {
|
|
136
137
|
check(req) {
|
|
137
|
-
if (req
|
|
138
|
+
if (!callAnswersTo(req, ASK_USER_QUESTION_TOOL_NAME)) {
|
|
138
139
|
return { action: "allow" };
|
|
139
140
|
}
|
|
140
141
|
return { action: "ask", message: "AskUserQuestion: awaiting a human answer (durable)" };
|
|
@@ -868,6 +868,10 @@ export type PendingAction = {
|
|
|
868
868
|
*/
|
|
869
869
|
family?: import("./tool-face.js").ToolFamily;
|
|
870
870
|
pathTarget?: import("./tool-face.js").ToolRosterPathTarget;
|
|
871
|
+
/** The names a rule may spell the parked tool by (`ToolCallFace.names`: the wire name, then the aliases that resolve
|
|
872
|
+
* to it) off the same roster row at the same mint — the pre-CAS recheck judges an edit against rules written on
|
|
873
|
+
* either name, as the live gate judged the original. Absent on a row minted before it ⇒ the row's `toolName` alone. */
|
|
874
|
+
names?: readonly string[];
|
|
871
875
|
/**
|
|
872
876
|
* #438 (additive; no checkpoint-version bump — the `reminderMark` precedent: an optional field an
|
|
873
877
|
* older reader ignores. Unlike that precedent seat — which resume DOES read, through its
|
package/dist/core/gate-fold.js
CHANGED
|
@@ -50,7 +50,7 @@ export async function runGateFold(pass) {
|
|
|
50
50
|
}
|
|
51
51
|
if ((pass.decision.action === "allow" || pass.decision.action === "ask") && input.shellReadCheck !== undefined) {
|
|
52
52
|
const station = input.shellReadCheck;
|
|
53
|
-
const judged = station.judge(
|
|
53
|
+
const judged = station.judge(pass.req, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput, input.trackedCwd?.(), pass.callSignal ?? input.abortSignal, { toolCallId });
|
|
54
54
|
if (judged !== null) {
|
|
55
55
|
let verdict;
|
|
56
56
|
try {
|
|
@@ -61,7 +61,7 @@ export async function runGateFold(pass) {
|
|
|
61
61
|
if (kind !== "aborted" && input.abortSignal?.aborted !== true) {
|
|
62
62
|
traceHookCrash(input, new Error(`shell read station ${kind === "timeout" ? "timed out" : "threw"} for tool "${toolName}" (toolCallId ${toolCallId}) — where this call reads could not be established: ${thrownText(err)}`), notifier);
|
|
63
63
|
}
|
|
64
|
-
verdict = station.unanswered(
|
|
64
|
+
verdict = station.unanswered(pass.req, kind === "timeout" ? "the read station timed out" : "the read station could not answer");
|
|
65
65
|
}
|
|
66
66
|
applyShellReadVerdict(pass, verdict, toolName, input.shellGated === true);
|
|
67
67
|
}
|
package/dist/core/gate-lanes.js
CHANGED
|
@@ -11,6 +11,7 @@ import { CD_GIT_COMPOUND_SENTENCE, changesDirectoryBeforeGit, readOnlyShellVerdi
|
|
|
11
11
|
import { readShellCommand } from "./shell-lexer.js";
|
|
12
12
|
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
13
13
|
import { catalogRuleFaceOf } from "./tool-registry.js";
|
|
14
|
+
import { callAnswersTo, callFaceSeat } from "./tool-roster.js";
|
|
14
15
|
import { modeAcceptsFileCommand } from "./mode-policy.js";
|
|
15
16
|
import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
|
|
16
17
|
import { exitGate } from "./runner/gate-exit.js";
|
|
@@ -169,7 +170,7 @@ export async function runGateLanes(pass) {
|
|
|
169
170
|
writeProtectionUnresolved: pass.writeProtectionUnresolved === true,
|
|
170
171
|
});
|
|
171
172
|
const askOriginFacts = (org, ruleCallUnreadable) => ({
|
|
172
|
-
contentQuestion:
|
|
173
|
+
contentQuestion: callAnswersTo(pass.req, ASK_USER_QUESTION_TOOL_NAME),
|
|
173
174
|
markedUnresolvable: markedUnresolvable(),
|
|
174
175
|
org,
|
|
175
176
|
...(ruleCallUnreadable ? { ruleCallUnreadable } : {}),
|
|
@@ -199,7 +200,7 @@ export async function runGateLanes(pass) {
|
|
|
199
200
|
let decided = current;
|
|
200
201
|
let pending;
|
|
201
202
|
try {
|
|
202
|
-
pending = Promise.resolve(input.orgRules.adjudicate({ toolName, args, toolCallId }));
|
|
203
|
+
pending = Promise.resolve(input.orgRules.adjudicate({ toolName, args, toolCallId, ...callFaceSeat(pass.req.face) }));
|
|
203
204
|
}
|
|
204
205
|
catch (err) {
|
|
205
206
|
pending = Promise.resolve({ status: "unavailable", disclosures: [`the org adjudication face threw before answering: ${describeThrown(err)}`] });
|
|
@@ -767,14 +768,14 @@ export async function runGateLanes(pass) {
|
|
|
767
768
|
}
|
|
768
769
|
if (editDenied === undefined && input.shellReadCheck !== undefined) {
|
|
769
770
|
const station = input.shellReadCheck;
|
|
770
|
-
const judged = station.judge(
|
|
771
|
+
const judged = station.judge(pass.req, editArgs, input.trackedCwd?.(), callSignal ?? input.abortSignal, { seat: "rejudge", toolCallId });
|
|
771
772
|
if (judged !== null) {
|
|
772
773
|
let verdict;
|
|
773
774
|
try {
|
|
774
775
|
verdict = await withGateTimeout(judged, boundedAwaitDeadlineMs(), callSignal ?? input.abortSignal, "shellReadStation");
|
|
775
776
|
}
|
|
776
777
|
catch {
|
|
777
|
-
verdict = station.unanswered(
|
|
778
|
+
verdict = station.unanswered(pass.req, "the read station could not answer");
|
|
778
779
|
}
|
|
779
780
|
if (verdict.disposition !== "vouch") {
|
|
780
781
|
editDenied = {
|
|
@@ -12,7 +12,6 @@ export declare const GOVERNANCE_CODES: {
|
|
|
12
12
|
readonly "config.compliance_required": "transient";
|
|
13
13
|
readonly "config.compliance_denied": "terminal";
|
|
14
14
|
readonly "config.locked_key": "terminal";
|
|
15
|
-
readonly "config.tool_mount_denied": "terminal";
|
|
16
15
|
readonly "memory.capture_optout_denied": "terminal";
|
|
17
16
|
};
|
|
18
17
|
export type GovernanceCode = keyof typeof GOVERNANCE_CODES;
|
|
@@ -42,6 +41,23 @@ export declare const NON_GOVERNANCE_MEMORY_CODES: ReadonlySet<string>;
|
|
|
42
41
|
* `undefined` (NOT governance — the caller falls through to its ordinary taxonomy).
|
|
43
42
|
*/
|
|
44
43
|
export declare function governanceRetryClass(code: string): GovernanceRetryClass | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The CLOSED set of codes the `spec.tools` DECLARATION door refuses a caller's tool entry with, at prepare,
|
|
46
|
+
* before anything mounts: the entry cannot be built from its own members (`config.tool_mount_denied`), a
|
|
47
|
+
* member over a roster row's wire bound (`config.tool_roster_bound` / `config.tool_name_too_long` /
|
|
48
|
+
* `config.tool_contract_too_long`), a schema over the row's key bounds (`config.tool_schema_bound`), a
|
|
49
|
+
* declared face or result card the schema / namespace cannot carry (`config.tool_face_invalid`), a name or
|
|
50
|
+
* alias in the engine's protocol namespace (`config.tool_name_invalid`), an egress tool declared other than
|
|
51
|
+
* `write` (`config.egress_requires_write_effect` — also minted, same meaning, for a protocol tool's resolved
|
|
52
|
+
* axes), a name the run reserves for a tool the engine injects (`config.reserved_tool_name` — structured output,
|
|
53
|
+
* the skill tool, tool search, the offload reader, an injected protocol tool; `runGoal`'s completion tool, which
|
|
54
|
+
* refuses before any run). Each ends the run `failed` with the code on `terminal.code`. A caller-shape refusal, not a policy
|
|
55
|
+
* verdict: none of these is a governance code ({@link GOVERNANCE_CODES}); re-issuing the same declaration
|
|
56
|
+
* changes nothing. Every mint site at the door types its code against this set.
|
|
57
|
+
*/
|
|
58
|
+
export declare const TOOL_DECLARATION_CODES: readonly ["config.tool_mount_denied", "config.tool_roster_bound", "config.tool_name_too_long", "config.tool_contract_too_long", "config.tool_schema_bound", "config.tool_face_invalid", "config.tool_name_invalid", "config.egress_requires_write_effect", "config.reserved_tool_name"];
|
|
59
|
+
/** A code in {@link TOOL_DECLARATION_CODES}. */
|
|
60
|
+
export type ToolDeclarationCode = (typeof TOOL_DECLARATION_CODES)[number];
|
|
45
61
|
/**
|
|
46
62
|
* The CLOSED registry of reasons a permission-rule sync round may refuse an inbound record or move a
|
|
47
63
|
* local row into the quarantine area (design/182 §4.3/§5.2/§8.1). Same doctrine as
|
|
@@ -5,7 +5,6 @@ export const GOVERNANCE_CODES = {
|
|
|
5
5
|
"config.compliance_required": "transient",
|
|
6
6
|
"config.compliance_denied": "terminal",
|
|
7
7
|
"config.locked_key": "terminal",
|
|
8
|
-
"config.tool_mount_denied": "terminal",
|
|
9
8
|
"memory.capture_optout_denied": "terminal",
|
|
10
9
|
};
|
|
11
10
|
export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
@@ -93,6 +92,17 @@ export function governanceRetryClass(code) {
|
|
|
93
92
|
return "terminal";
|
|
94
93
|
return undefined;
|
|
95
94
|
}
|
|
95
|
+
export const TOOL_DECLARATION_CODES = [
|
|
96
|
+
"config.tool_mount_denied",
|
|
97
|
+
"config.tool_roster_bound",
|
|
98
|
+
"config.tool_name_too_long",
|
|
99
|
+
"config.tool_contract_too_long",
|
|
100
|
+
"config.tool_schema_bound",
|
|
101
|
+
"config.tool_face_invalid",
|
|
102
|
+
"config.tool_name_invalid",
|
|
103
|
+
"config.egress_requires_write_effect",
|
|
104
|
+
"config.reserved_tool_name",
|
|
105
|
+
];
|
|
96
106
|
export const RULE_SYNC_DROP_CODES = {
|
|
97
107
|
invalid_rule_text: "inbound-refused-or-local-quarantined",
|
|
98
108
|
metadata_mismatch: "inbound-refused-or-local-quarantined",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -1111,6 +1111,9 @@ export interface ToolGateInput {
|
|
|
1111
1111
|
* isolation scope (onHookError itself, the deny observer, the durable-park observer). The runner
|
|
1112
1112
|
* wires this to an `observer.notify_failed` trace event; absent = counted only (unchanged). */
|
|
1113
1113
|
onNotifyError?: (failure: import("./safe-notify.js").SafeNotifyFailure) => void;
|
|
1114
|
+
/** The call. `toolName` is the name every policy, hook and name-keyed table of this gate reads — the wire name of the
|
|
1115
|
+
* tool the call dispatches to: the Runner's station resolves a call made under an alias through the roster
|
|
1116
|
+
* (`rosterToolName`) before this gate runs; a host driving the gate resolves it the same way. */
|
|
1114
1117
|
event: {
|
|
1115
1118
|
toolCallId: string;
|
|
1116
1119
|
toolName: string;
|
|
@@ -10,7 +10,8 @@ export declare function messageRole(m: AgentMessage): string | undefined;
|
|
|
10
10
|
* same terms rather than on four. The READ twin of `noteResponseModel` (`brain/stream-shared.ts`), which
|
|
11
11
|
* states the same law on the write side; this side re-tests rather than trusting, because a HOST's own
|
|
12
12
|
* Brain mints these messages too. `undefined` ⇒ the provider named the stamped id, named nothing, or
|
|
13
|
-
* named something unreadable — ONE reading, never three.
|
|
13
|
+
* named something unreadable — ONE reading, never three. Readable = {@link isProviderEchoShape}, the write
|
|
14
|
+
* twin's own predicate.
|
|
14
15
|
*/
|
|
15
16
|
export declare function providerReportedModel(m: {
|
|
16
17
|
readonly model?: unknown;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { isProviderEchoShape } from "../brain/errors.js";
|
|
1
2
|
export function messageRole(m) {
|
|
2
3
|
return m.role;
|
|
3
4
|
}
|
|
4
5
|
export function providerReportedModel(m) {
|
|
5
6
|
const reported = m.responseModel;
|
|
6
|
-
if (typeof reported !== "string" || reported
|
|
7
|
+
if (typeof reported !== "string" || !isProviderEchoShape(reported))
|
|
7
8
|
return undefined;
|
|
8
9
|
return reported === m.model ? undefined : reported;
|
|
9
10
|
}
|
|
@@ -172,6 +172,9 @@ export declare class PermissionModeController {
|
|
|
172
172
|
get current(): PermissionMode;
|
|
173
173
|
/** The three-member answer as it stands right now. */
|
|
174
174
|
receipt(): PermissionModeReceipt;
|
|
175
|
+
/** The ceiling's source moved to another leg (the parent session's next leg); `before` is this leg's receipt under the
|
|
176
|
+
* previous one. A ceiling fold the move produced — or changed — is announced, as the construction-time fold is. */
|
|
177
|
+
ceilingRebound(before: PermissionModeReceipt): void;
|
|
175
178
|
/** Bind the deployment's arming facts (once, after the caps resolved); announces a fold-back if one results. */
|
|
176
179
|
bindArming(arming: PermissionModeArming): PermissionModeReceipt;
|
|
177
180
|
/** A steer: the requested word changes; the arming fold is re-judged; a leg that became `auto` arms its classifier.
|
package/dist/core/mode-policy.js
CHANGED
|
@@ -136,6 +136,11 @@ export class PermissionModeController {
|
|
|
136
136
|
return { ...base, effective: declared, foldedBy: "inherited_ceiling" };
|
|
137
137
|
return { ...base, effective: declared };
|
|
138
138
|
}
|
|
139
|
+
ceilingRebound(before) {
|
|
140
|
+
const after = this.receipt();
|
|
141
|
+
if (after.foldedBy === "inherited_ceiling" && (before.foldedBy !== "inherited_ceiling" || before.effective !== after.effective))
|
|
142
|
+
this.onFold?.(after);
|
|
143
|
+
}
|
|
139
144
|
bindArming(arming) {
|
|
140
145
|
const before = this.receipt();
|
|
141
146
|
this.arming = arming;
|
|
@@ -141,6 +141,10 @@ export declare const READ_RULE_TOOL = "Read";
|
|
|
141
141
|
* catalog's declarations, exactly as the protective fences' covered set does.
|
|
142
142
|
*/
|
|
143
143
|
export declare function ruleToolGrammarOf(toolName: string): "command" | "path" | undefined;
|
|
144
|
+
/** The names the persisted grammar judges a call under: every one of the call's rule names (`ruleNamesOf` — the wire
|
|
145
|
+
* name, then the aliases that resolve to it) the grammar speaks for, so a stored `Bash(…)` or `Edit(…)` row reaches a
|
|
146
|
+
* tool that answers to that name under another wire name, as a policy's rule does. Empty ⇒ the grammar speaks for none. */
|
|
147
|
+
export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").ToolCallRequest, "toolName" | "face">): readonly string[];
|
|
144
148
|
/**
|
|
145
149
|
* Match forms. Four members, two of them one predicate:
|
|
146
150
|
* · `exact` — admits the one string the rule spells.
|
|
@@ -4,6 +4,7 @@ import { compileReadDeny } from "../tools/fs/read-deny.js";
|
|
|
4
4
|
import { carriesShellRedirection, parseLeadingCommandName, splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
|
|
5
5
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { pathTargetOf } from "./tool-registry.js";
|
|
7
|
+
import { ruleNamesOf } from "./tool-roster.js";
|
|
7
8
|
import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
|
|
8
9
|
import { readShellCommand } from "./shell-lexer.js";
|
|
9
10
|
import { readOnlyLineObjection, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
@@ -30,6 +31,9 @@ export function ruleToolGrammarOf(toolName) {
|
|
|
30
31
|
return "command";
|
|
31
32
|
return pathTargetOf(toolName) !== undefined ? "path" : undefined;
|
|
32
33
|
}
|
|
34
|
+
export function laneToolNamesOf(req) {
|
|
35
|
+
return ruleNamesOf(req).filter((n) => ruleToolGrammarOf(n) !== undefined);
|
|
36
|
+
}
|
|
33
37
|
export const MAX_RULE_TEXT_CHARS = 512;
|
|
34
38
|
export const BARE_INTERPRETER_NAMES = new Set([
|
|
35
39
|
"node", "deno", "bun", "python", "python2", "python3", "perl", "ruby", "php", "osascript",
|
|
@@ -6,6 +6,7 @@ import { pathFamilyOf, pathIdentityOf, spellingOfIdentity } from "../tools/fs/sa
|
|
|
6
6
|
import { effectivePathTargetOf } from "./effective-path-target.js";
|
|
7
7
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
8
8
|
import { ruleDenialSentence } from "./rule-denial-sentence.js";
|
|
9
|
+
import { ruleNamesOf } from "./tool-roster.js";
|
|
9
10
|
export { parsePermissionRule };
|
|
10
11
|
export const BASH_GENERIC_PARAMS = new Set(catalogRuleFaceOf("Bash")?.params ?? []);
|
|
11
12
|
const DEFAULT_CAPS = {
|
|
@@ -379,11 +380,30 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
379
380
|
ruleUnreadable: lane,
|
|
380
381
|
requiresRealApproval: true,
|
|
381
382
|
});
|
|
383
|
+
const entryFor = (req) => {
|
|
384
|
+
const entries = ruleNamesOf(req).flatMap((n) => {
|
|
385
|
+
const e = byTool.get(n);
|
|
386
|
+
return e !== undefined ? [e] : [];
|
|
387
|
+
});
|
|
388
|
+
if (entries.length <= 1)
|
|
389
|
+
return entries[0];
|
|
390
|
+
const bare = {};
|
|
391
|
+
for (const b of ["deny", "ask", "allow"]) {
|
|
392
|
+
const hit = entries.find((e) => e.bare[b] !== undefined)?.bare[b];
|
|
393
|
+
if (hit !== undefined)
|
|
394
|
+
bare[b] = hit;
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
bare,
|
|
398
|
+
param: { deny: entries.flatMap((e) => e.param.deny), ask: entries.flatMap((e) => e.param.ask) },
|
|
399
|
+
content: { deny: entries.flatMap((e) => e.content.deny), ask: entries.flatMap((e) => e.content.ask) },
|
|
400
|
+
};
|
|
401
|
+
};
|
|
382
402
|
return {
|
|
383
403
|
nameSets: [nameSets],
|
|
384
404
|
...(pathDenyRules.length > 0 ? { pathDenyRules } : {}),
|
|
385
405
|
check(req) {
|
|
386
|
-
const entry =
|
|
406
|
+
const entry = entryFor(req);
|
|
387
407
|
const covering = hasCovering && protocolOf(req.toolName) !== undefined;
|
|
388
408
|
if (entry?.bare.deny) {
|
|
389
409
|
return { action: "deny", message: ruleDenial(req, entry.bare.deny.ruleText, entry.bare.deny.source) };
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* (mandates, the org latch, the marked-call seat) rather than a function of the answer alone.
|
|
13
13
|
*/
|
|
14
14
|
import type { PermissionResult } from "./tool-policy.js";
|
|
15
|
-
import type { PersistedRuleHit, PersistedRuleHitRule } from "./hooks.js";
|
|
15
|
+
import type { PersistedRuleAnswer, PersistedRuleHit, PersistedRuleHitRule } from "./hooks.js";
|
|
16
16
|
/** The lane's one read, normalized: at most one of `hit` / `unreadable` / `coverage` (the gate's reader
|
|
17
17
|
* produces it; the arms below read it). */
|
|
18
18
|
export interface PersistedRuleRead {
|
|
@@ -23,6 +23,9 @@ export interface PersistedRuleRead {
|
|
|
23
23
|
/** The lexer's reason when the CALL could not be read against the person's deny/ask rows. */
|
|
24
24
|
readonly reason?: string;
|
|
25
25
|
}
|
|
26
|
+
/** The answers of one store read judged under each of a call's names, folded as one table folds its rows (deny >
|
|
27
|
+
* unreadable > ask > allow > coverage > nothing); among equals the first (the wire name's) stands. */
|
|
28
|
+
export declare function strongestAnswer(answers: readonly PersistedRuleAnswer[]): PersistedRuleAnswer;
|
|
26
29
|
/** Display form of a rule set (design/375 §5.1): every rule text is a DISPLAY value on a human trust
|
|
27
30
|
* boundary, and the set must stay readable however large a lane makes it — each member rides
|
|
28
31
|
* `inlineUntrusted` (200 cap, the same cap the durable leg's `RiskDescriptor.shadowedRule` carries),
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { decisionAskClassesOf } from "./ask-origin.js";
|
|
2
2
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
3
|
+
export function strongestAnswer(answers) {
|
|
4
|
+
const rank = (a) => (a === undefined ? 0 : "unreadable" in a ? 4 : "behavior" in a ? { deny: 5, ask: 3, allow: 2 }[a.behavior] : 1);
|
|
5
|
+
return answers.reduce((best, a) => (rank(a) > rank(best) ? a : best), undefined);
|
|
6
|
+
}
|
|
3
7
|
export function disclosedRuleSet(rules) {
|
|
4
8
|
const shown = rules.slice(0, 5).map((r) => inlineUntrusted(r.rule, 200));
|
|
5
9
|
const rest = rules.length - shown.length;
|
|
@@ -15,5 +15,5 @@ export function assertNoRetiredKeys(bag, table, owner) {
|
|
|
15
15
|
}
|
|
16
16
|
export const RETIRED_TASK_SPEC_KEYS = Object.freeze({
|
|
17
17
|
autoModeRequested: 'the permission mode is one key now — write `permissionMode: "auto"`',
|
|
18
|
-
approverPosture: '
|
|
18
|
+
approverPosture: 'a posture never authorized anything; drop the key (the engine renders the posture from the effective mode). Releasing asks is a separate decision: `permissionMode: "bypassPermissions"`',
|
|
19
19
|
});
|
|
@@ -3,6 +3,7 @@ import { pathTargetValue } from "../tool-registry.js";
|
|
|
3
3
|
import { pathTargetBaseOf } from "../effective-path-target.js";
|
|
4
4
|
import { PATH_WRITE_TOOLS, isWithin } from "./session-rule-policy.js";
|
|
5
5
|
import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
|
|
6
|
+
import { ruleNamesOf } from "../tool-roster.js";
|
|
6
7
|
export class ActiveSkillScope {
|
|
7
8
|
frames = [];
|
|
8
9
|
push(frame) {
|
|
@@ -22,7 +23,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
22
23
|
const { scope, env, rootPath, toolEffects } = opts;
|
|
23
24
|
return {
|
|
24
25
|
async check(req, signal) {
|
|
25
|
-
const
|
|
26
|
+
const names = ruleNamesOf(req);
|
|
26
27
|
const frames = scope.active();
|
|
27
28
|
if (frames.length === 0)
|
|
28
29
|
return { action: "allow" };
|
|
@@ -47,7 +48,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
47
48
|
}
|
|
48
49
|
return { manifest: m, byName };
|
|
49
50
|
});
|
|
50
|
-
if (perFrame.length === 0 || perFrame.some((f) => !f.byName.has(
|
|
51
|
+
if (perFrame.length === 0 || perFrame.some((f) => !names.some((n) => f.byName.has(n)))) {
|
|
51
52
|
const ids = manifests.map((m) => m.lineageId).join(", ");
|
|
52
53
|
return {
|
|
53
54
|
action: "deny",
|
|
@@ -56,7 +57,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
for (const frame of perFrame) {
|
|
59
|
-
const alternatives = frame.byName.get(
|
|
60
|
+
const alternatives = names.flatMap((n) => frame.byName.get(n) ?? []);
|
|
60
61
|
const rejections = [];
|
|
61
62
|
let admitted = false;
|
|
62
63
|
for (const entry of alternatives) {
|
|
@@ -79,7 +80,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
79
80
|
const declaredTarget = req.face?.pathTarget;
|
|
80
81
|
const skillScopeEligible = declaredTarget !== undefined && declaredTarget.skillScopeEligible === true;
|
|
81
82
|
if (pathConstrainingActive && !skillScopeEligible) {
|
|
82
|
-
const eff = toolEffects?.get(toolName) ?? "write";
|
|
83
|
+
const eff = toolEffects?.get(req.toolName) ?? "write";
|
|
83
84
|
if (eff !== "read") {
|
|
84
85
|
const ids = manifests.filter((m) => m.allowPaths?.length).map((m) => m.lineageId).join(", ");
|
|
85
86
|
return {
|