pi-daddy 0.22.0 → 0.23.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 +30 -0
- package/contracts/daily-view/v1/README.md +114 -0
- package/contracts/daily-view/v1/fixtures/view.json +525 -0
- package/contracts/daily-view/v1/fixtures/view.txt +75 -0
- package/contracts/daily-view/v1/fixtures/work.jsonl +17 -0
- package/contracts/daily-view/v1/p03/README.md +26 -0
- package/contracts/daily-view/v1/p03/fixtures/retained-executions.json +241 -0
- package/contracts/daily-view/v1/p03/projection.schema.json +194 -0
- package/contracts/daily-view/v1/p03/provenance.json +21 -0
- package/contracts/dashboard-host/v1/README.md +176 -0
- package/contracts/debrief/v1/README.md +146 -0
- package/contracts/debrief/v1/intervention/README.md +32 -0
- package/contracts/debrief/v1/intervention/fixtures/artifact-3ee11136beb864f518252e5024dae9197421fbf97b6e981d3243c782b5c44bd0.txt +1 -0
- package/contracts/debrief/v1/intervention/fixtures/artifact-58905d872dd3c54a9705f67b05fa82edbad7af6476ae8993faf1a7b9fadfb905.txt +1 -0
- package/contracts/debrief/v1/intervention/fixtures/assessment.json +29 -0
- package/contracts/debrief/v1/intervention/fixtures/blind-view.json +20 -0
- package/contracts/debrief/v1/intervention/fixtures/choice.json +7 -0
- package/contracts/debrief/v1/intervention/fixtures/manifest.json +57 -0
- package/contracts/debrief/v1/intervention/fixtures/reveal.json +39 -0
- package/contracts/debrief/v1/provenance.json +89 -0
- package/contracts/debrief/v1/work-capture/README.md +38 -0
- package/contracts/debrief/v1/work-capture/fixtures/batch.json +17 -0
- package/contracts/debrief/v1/work-capture/fixtures/cases.json +184 -0
- package/contracts/debrief/v1/work-capture/fixtures/review.json +184 -0
- package/contracts/debrief/v1/work-capture/review-request.schema.json +41 -0
- package/contracts/debrief/v1/work-capture/work-case.schema.json +190 -0
- package/contracts/debrief/v2/README.md +71 -0
- package/contracts/debrief/v2/input-pin.json +66 -0
- package/contracts/debrief/v2/intervention-upstream.md +40 -0
- package/contracts/debrief/v2/work-signals-upstream.md +22 -0
- package/contracts/dispatch-control/v1/README.md +109 -0
- package/contracts/dispatch-control/v1/request.schema.json +19 -0
- package/contracts/effect-profile/v1/README.md +124 -0
- package/contracts/execution-retention/v1/README.md +109 -0
- package/contracts/execution-retention/v2/README.md +148 -0
- package/contracts/execution-retention/v2/fixtures/interrupted-native-bytes.json +91 -0
- package/contracts/execution-retention/v2/fixtures/missing-native-session.json +91 -0
- package/contracts/execution-retention/v2/fixtures/native-file-unknown-branch.json +91 -0
- package/contracts/execution-retention/v2/fixtures/native-id-without-bytes.json +91 -0
- package/contracts/execution-retention/v2/fixtures/native-live-branch.json +90 -0
- package/contracts/execution-retention/v2/fixtures/replaced-native-session.json +92 -0
- package/contracts/execution-retention/v2/fixtures/result-82c620cdfeb8ca6b858cf653d7f5e2eedca5315d6244216d60e8b36f03223d6a.bin +1 -0
- package/contracts/execution-retention/v2/fixtures/session-2ec1f134bc739984434f7fca689e1c3d1c39fce2dcadae709e9b6faedfcc4ac7.bin +3 -0
- package/contracts/execution-retention/v2/fixtures/session-9bdb4bd16a901da31580acf056bda525225259e15ec409e0410480109011772f.bin +3 -0
- package/contracts/execution-retention/v2/manifest.schema.json +858 -0
- package/contracts/experiment/v1/README.md +164 -0
- package/contracts/factory-order/v1/README.md +151 -0
- package/contracts/factory-order/v1/adoption-pin.json +8 -0
- package/contracts/intent-control/v1/README.md +135 -0
- package/contracts/intent-control/v1/request.schema.json +1096 -0
- package/contracts/intent-control/v2/README.md +34 -0
- package/contracts/intent-control/v2/request.schema.json +1070 -0
- package/contracts/ledger/v4/README.md +241 -0
- package/contracts/ledger/v4/fixtures/layout-options.json +428 -0
- package/contracts/ledger/v4/fixtures/work-acceptance.json +57 -0
- package/contracts/ledger/v4/fixtures/work-occurrence.json +44 -0
- package/contracts/ledger/v4/fixtures/work-revision.json +26 -0
- package/contracts/ledger/v4/fixtures/work-snapshot.json +73 -0
- package/contracts/ledger/v4/ledger-event.schema.json +162 -0
- package/contracts/ordinary-control/v1/README.md +59 -0
- package/contracts/producer-ipc/v1/README.md +137 -0
- package/dist/check-runner.d.ts +5 -0
- package/dist/check-runner.d.ts.map +1 -1
- package/dist/check-runner.js +16 -2
- package/dist/check-runner.js.map +1 -1
- package/dist/control-journal.d.ts +20 -0
- package/dist/control-journal.d.ts.map +1 -0
- package/dist/control-journal.js +73 -0
- package/dist/control-journal.js.map +1 -0
- package/dist/daily-view-input.d.ts +44 -0
- package/dist/daily-view-input.d.ts.map +1 -0
- package/dist/daily-view-input.js +68 -0
- package/dist/daily-view-input.js.map +1 -0
- package/dist/daily-view-render.d.ts +4 -0
- package/dist/daily-view-render.d.ts.map +1 -0
- package/dist/daily-view-render.js +41 -0
- package/dist/daily-view-render.js.map +1 -0
- package/dist/daily-view.d.ts +74 -0
- package/dist/daily-view.d.ts.map +1 -0
- package/dist/daily-view.js +170 -0
- package/dist/daily-view.js.map +1 -0
- package/dist/dashboard-cli.d.ts +26 -1
- package/dist/dashboard-cli.d.ts.map +1 -1
- package/dist/dashboard-cli.js +121 -3
- package/dist/dashboard-cli.js.map +1 -1
- package/dist/dashboard-harness.d.ts +19 -0
- package/dist/dashboard-harness.d.ts.map +1 -0
- package/dist/dashboard-harness.js +75 -0
- package/dist/dashboard-harness.js.map +1 -0
- package/dist/dashboard-herdr.d.ts.map +1 -1
- package/dist/dashboard-herdr.js +7 -2
- package/dist/dashboard-herdr.js.map +1 -1
- package/dist/dashboard-host-contract.d.ts +111 -0
- package/dist/dashboard-host-contract.d.ts.map +1 -0
- package/dist/dashboard-host-contract.js +14 -0
- package/dist/dashboard-host-contract.js.map +1 -0
- package/dist/dashboard-host-transport.d.ts +15 -0
- package/dist/dashboard-host-transport.d.ts.map +1 -0
- package/dist/dashboard-host-transport.js +122 -0
- package/dist/dashboard-host-transport.js.map +1 -0
- package/dist/dashboard-host.d.ts +149 -0
- package/dist/dashboard-host.d.ts.map +1 -0
- package/dist/dashboard-host.js +329 -0
- package/dist/dashboard-host.js.map +1 -0
- package/dist/dashboard-observation.d.ts +28 -0
- package/dist/dashboard-observation.d.ts.map +1 -0
- package/dist/dashboard-observation.js +86 -0
- package/dist/dashboard-observation.js.map +1 -0
- package/dist/debrief-contract.d.ts +65 -0
- package/dist/debrief-contract.d.ts.map +1 -0
- package/dist/debrief-contract.js +151 -0
- package/dist/debrief-contract.js.map +1 -0
- package/dist/debrief-fixture.d.ts +16 -0
- package/dist/debrief-fixture.d.ts.map +1 -0
- package/dist/debrief-fixture.js +41 -0
- package/dist/debrief-fixture.js.map +1 -0
- package/dist/debrief-host.d.ts +59 -0
- package/dist/debrief-host.d.ts.map +1 -0
- package/dist/debrief-host.js +49 -0
- package/dist/debrief-host.js.map +1 -0
- package/dist/debrief-render.d.ts +5 -0
- package/dist/debrief-render.d.ts.map +1 -0
- package/dist/debrief-render.js +65 -0
- package/dist/debrief-render.js.map +1 -0
- package/dist/debrief.d.ts +90 -0
- package/dist/debrief.d.ts.map +1 -0
- package/dist/debrief.js +263 -0
- package/dist/debrief.js.map +1 -0
- package/dist/delegate-types.d.ts +2 -0
- package/dist/delegate-types.d.ts.map +1 -1
- package/dist/delegate-types.js.map +1 -1
- package/dist/delegate.d.ts.map +1 -1
- package/dist/delegate.js +1 -0
- package/dist/delegate.js.map +1 -1
- package/dist/dispatch-control.d.ts +45 -0
- package/dist/dispatch-control.d.ts.map +1 -0
- package/dist/dispatch-control.js +76 -0
- package/dist/dispatch-control.js.map +1 -0
- package/dist/effect-profile-runtime.d.ts +40 -0
- package/dist/effect-profile-runtime.d.ts.map +1 -0
- package/dist/effect-profile-runtime.js +51 -0
- package/dist/effect-profile-runtime.js.map +1 -0
- package/dist/effect-profile.d.ts +40 -0
- package/dist/effect-profile.d.ts.map +1 -0
- package/dist/effect-profile.js +152 -0
- package/dist/effect-profile.js.map +1 -0
- package/dist/execution-retention.d.ts +90 -0
- package/dist/execution-retention.d.ts.map +1 -0
- package/dist/execution-retention.js +247 -0
- package/dist/execution-retention.js.map +1 -0
- package/dist/experiment-contract.d.ts +56 -0
- package/dist/experiment-contract.d.ts.map +1 -0
- package/dist/experiment-contract.js +80 -0
- package/dist/experiment-contract.js.map +1 -0
- package/dist/experiment-state.d.ts +22 -0
- package/dist/experiment-state.d.ts.map +1 -0
- package/dist/experiment-state.js +79 -0
- package/dist/experiment-state.js.map +1 -0
- package/dist/experiment-store.d.ts +28 -0
- package/dist/experiment-store.d.ts.map +1 -0
- package/dist/experiment-store.js +130 -0
- package/dist/experiment-store.js.map +1 -0
- package/dist/experiment.d.ts +65 -0
- package/dist/experiment.d.ts.map +1 -0
- package/dist/experiment.js +357 -0
- package/dist/experiment.js.map +1 -0
- package/dist/factory-contract.d.ts +79 -0
- package/dist/factory-contract.d.ts.map +1 -0
- package/dist/factory-contract.js +89 -0
- package/dist/factory-contract.js.map +1 -0
- package/dist/factory-migration.d.ts +20 -0
- package/dist/factory-migration.d.ts.map +1 -0
- package/dist/factory-migration.js +55 -0
- package/dist/factory-migration.js.map +1 -0
- package/dist/factory-order.d.ts +848 -0
- package/dist/factory-order.d.ts.map +1 -0
- package/dist/factory-order.js +83 -0
- package/dist/factory-order.js.map +1 -0
- package/dist/factory-registry.d.ts +121 -0
- package/dist/factory-registry.d.ts.map +1 -0
- package/dist/factory-registry.js +133 -0
- package/dist/factory-registry.js.map +1 -0
- package/dist/file-lock.d.ts +8 -2
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +78 -19
- package/dist/file-lock.js.map +1 -1
- package/dist/herdr-poll.d.ts +8 -0
- package/dist/herdr-poll.d.ts.map +1 -1
- package/dist/herdr-poll.js +23 -0
- package/dist/herdr-poll.js.map +1 -1
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/dist/intent-application.d.ts +17 -0
- package/dist/intent-application.d.ts.map +1 -0
- package/dist/intent-application.js +113 -0
- package/dist/intent-application.js.map +1 -0
- package/dist/intent-control.d.ts +201 -0
- package/dist/intent-control.d.ts.map +1 -0
- package/dist/intent-control.js +110 -0
- package/dist/intent-control.js.map +1 -0
- package/dist/intent-scheduling.d.ts +9 -0
- package/dist/intent-scheduling.d.ts.map +1 -0
- package/dist/intent-scheduling.js +15 -0
- package/dist/intent-scheduling.js.map +1 -0
- package/dist/ledger-append.d.ts +15 -0
- package/dist/ledger-append.d.ts.map +1 -0
- package/dist/ledger-append.js +94 -0
- package/dist/ledger-append.js.map +1 -0
- package/dist/ledger.d.ts +11 -0
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +3 -7
- package/dist/ledger.js.map +1 -1
- package/dist/native-session-target.d.ts +9 -0
- package/dist/native-session-target.d.ts.map +1 -0
- package/dist/native-session-target.js +29 -0
- package/dist/native-session-target.js.map +1 -0
- package/dist/native-session.d.ts +59 -0
- package/dist/native-session.d.ts.map +1 -0
- package/dist/native-session.js +154 -0
- package/dist/native-session.js.map +1 -0
- package/dist/order-schedule.d.ts +37 -0
- package/dist/order-schedule.d.ts.map +1 -0
- package/dist/order-schedule.js +81 -0
- package/dist/order-schedule.js.map +1 -0
- package/dist/ordinary-children.d.ts +52 -0
- package/dist/ordinary-children.d.ts.map +1 -0
- package/dist/ordinary-children.js +93 -0
- package/dist/ordinary-children.js.map +1 -0
- package/dist/producer-ipc-contract.d.ts +29 -0
- package/dist/producer-ipc-contract.d.ts.map +1 -0
- package/dist/producer-ipc-contract.js +29 -0
- package/dist/producer-ipc-contract.js.map +1 -0
- package/dist/producer-ipc.d.ts +55 -0
- package/dist/producer-ipc.d.ts.map +1 -0
- package/dist/producer-ipc.js +143 -0
- package/dist/producer-ipc.js.map +1 -0
- package/dist/resource-budget.d.ts +334 -0
- package/dist/resource-budget.d.ts.map +1 -0
- package/dist/resource-budget.js +415 -0
- package/dist/resource-budget.js.map +1 -0
- package/dist/retention-contract.d.ts +73 -0
- package/dist/retention-contract.d.ts.map +1 -0
- package/dist/retention-contract.js +104 -0
- package/dist/retention-contract.js.map +1 -0
- package/dist/retention-json.d.ts +3 -0
- package/dist/retention-json.d.ts.map +1 -0
- package/dist/retention-json.js +32 -0
- package/dist/retention-json.js.map +1 -0
- package/dist/run-child.d.ts +4 -0
- package/dist/run-child.d.ts.map +1 -1
- package/dist/run-child.js +16 -2
- package/dist/run-child.js.map +1 -1
- package/dist/run-herdr.d.ts +6 -0
- package/dist/run-herdr.d.ts.map +1 -1
- package/dist/run-herdr.js +19 -6
- package/dist/run-herdr.js.map +1 -1
- package/dist/vendor/adoption.d.ts +129 -0
- package/dist/vendor/adoption.d.ts.map +1 -0
- package/dist/vendor/adoption.js +102 -0
- package/dist/vendor/adoption.js.map +1 -0
- package/dist/work-ledger-destination.d.ts +20 -0
- package/dist/work-ledger-destination.d.ts.map +1 -0
- package/dist/work-ledger-destination.js +131 -0
- package/dist/work-ledger-destination.js.map +1 -0
- package/dist/work-ledger-json.d.ts +25 -0
- package/dist/work-ledger-json.d.ts.map +1 -0
- package/dist/work-ledger-json.js +267 -0
- package/dist/work-ledger-json.js.map +1 -0
- package/dist/work-ledger-occurrences.d.ts +227 -0
- package/dist/work-ledger-occurrences.d.ts.map +1 -0
- package/dist/work-ledger-occurrences.js +115 -0
- package/dist/work-ledger-occurrences.js.map +1 -0
- package/dist/work-ledger-projection.d.ts +295 -0
- package/dist/work-ledger-projection.d.ts.map +1 -0
- package/dist/work-ledger-projection.js +237 -0
- package/dist/work-ledger-projection.js.map +1 -0
- package/dist/work-ledger-snapshot.d.ts +29 -0
- package/dist/work-ledger-snapshot.d.ts.map +1 -0
- package/dist/work-ledger-snapshot.js +267 -0
- package/dist/work-ledger-snapshot.js.map +1 -0
- package/dist/work-ledger-types.d.ts +261 -0
- package/dist/work-ledger-types.d.ts.map +1 -0
- package/dist/work-ledger-types.js +13 -0
- package/dist/work-ledger-types.js.map +1 -0
- package/dist/work-ledger-validation.d.ts +14 -0
- package/dist/work-ledger-validation.d.ts.map +1 -0
- package/dist/work-ledger-validation.js +226 -0
- package/dist/work-ledger-validation.js.map +1 -0
- package/dist/work-ledger.d.ts +43 -0
- package/dist/work-ledger.d.ts.map +1 -0
- package/dist/work-ledger.js +225 -0
- package/dist/work-ledger.js.map +1 -0
- package/extensions/delegate-chain.ts +1 -0
- package/extensions/delegation-native.ts +12 -0
- package/extensions/delegation.ts +4 -3
- package/extensions/execute-child.ts +34 -4
- package/extensions/grants.ts +2 -0
- package/extensions/ordinary-runtime.ts +14 -0
- package/extensions/run-delegation.ts +7 -7
- package/extensions/session.ts +3 -1
- package/package.json +78 -4
- package/src/check-runner.ts +20 -2
- package/src/control-journal.ts +26 -0
- package/src/daily-view-input.ts +59 -0
- package/src/daily-view-render.ts +44 -0
- package/src/daily-view.ts +152 -0
- package/src/dashboard-cli.ts +103 -4
- package/src/dashboard-harness.ts +28 -0
- package/src/dashboard-herdr.ts +7 -2
- package/src/dashboard-host-contract.ts +42 -0
- package/src/dashboard-host-transport.ts +38 -0
- package/src/dashboard-host.ts +168 -0
- package/src/dashboard-observation.ts +44 -0
- package/src/debrief-contract.ts +127 -0
- package/src/debrief-fixture.ts +29 -0
- package/src/debrief-host.ts +53 -0
- package/src/debrief-render.ts +49 -0
- package/src/debrief.ts +198 -0
- package/src/delegate-types.ts +2 -0
- package/src/delegate.ts +1 -0
- package/src/dispatch-control.ts +81 -0
- package/src/effect-profile-runtime.ts +46 -0
- package/src/effect-profile.ts +129 -0
- package/src/execution-retention.ts +230 -0
- package/src/experiment-contract.ts +66 -0
- package/src/experiment-state.ts +44 -0
- package/src/experiment-store.ts +90 -0
- package/src/experiment.ts +231 -0
- package/src/factory-contract.ts +47 -0
- package/src/factory-migration.ts +29 -0
- package/src/factory-order.ts +56 -0
- package/src/factory-registry.ts +60 -0
- package/src/file-lock.ts +47 -11
- package/src/herdr-poll.ts +18 -0
- package/src/index.ts +32 -0
- package/src/intent-application.ts +90 -0
- package/src/intent-control.ts +107 -0
- package/src/intent-scheduling.ts +14 -0
- package/src/ledger-append.ts +72 -0
- package/src/ledger.ts +3 -9
- package/src/native-session-target.ts +23 -0
- package/src/native-session.ts +139 -0
- package/src/order-schedule.ts +42 -0
- package/src/ordinary-children.ts +59 -0
- package/src/producer-ipc-contract.ts +26 -0
- package/src/producer-ipc.ts +107 -0
- package/src/resource-budget.ts +349 -0
- package/src/retention-contract.ts +84 -0
- package/src/retention-json.ts +20 -0
- package/src/run-child.ts +13 -2
- package/src/run-herdr.ts +11 -7
- package/src/vendor/adoption.ts +109 -0
- package/src/work-ledger-destination.ts +113 -0
- package/src/work-ledger-json.ts +228 -0
- package/src/work-ledger-occurrences.ts +99 -0
- package/src/work-ledger-projection.ts +262 -0
- package/src/work-ledger-snapshot.ts +246 -0
- package/src/work-ledger-types.ts +229 -0
- package/src/work-ledger-validation.ts +174 -0
- package/src/work-ledger.ts +186 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { dataDigest, detached, freeze, sha } from "./debrief-contract.ts";
|
|
2
|
+
import { controlShape } from "./dispatch-control.ts";
|
|
3
|
+
import type { DebriefHarness, DurableBlindBinding, CaseSelection } from "./debrief-host.ts";
|
|
4
|
+
import type { WorkProjectionContext, WorkFrozen } from "./work-ledger.ts";
|
|
5
|
+
import type { DispatchAuthority } from "./dispatch-control.ts";
|
|
6
|
+
import type { OrdinaryAuthority } from "./ordinary-children.ts";
|
|
7
|
+
import type { ExperimentAuthority } from "./experiment-contract.ts";
|
|
8
|
+
export const DASHBOARD_HARNESS_PIN = "1c02194d4a3709d14890a5fbbad91ff5f0151f65";
|
|
9
|
+
export interface HostEvent { id: string; prior: string | null; value: Record<string, unknown> }
|
|
10
|
+
export interface HostJournal { read(): HostEvent[]; append(prior: string, value: Record<string, unknown>): HostEvent }
|
|
11
|
+
/** Loaded trusted ports; an interface or caller-supplied pin is NOT module/human authentication. */
|
|
12
|
+
export interface DashboardHarness extends DebriefHarness {
|
|
13
|
+
learningJournal(path: string, initial?: Record<string, unknown>): HostJournal;
|
|
14
|
+
openTrustLifecycle(path: string): { history(): HostEvent[]; inspect(now: number): { policyId: string; attentionUsed: number; tip: string; grantExpansion: false }; expose(id: string, now: number): { mode: string; reason: string; replayed: boolean } };
|
|
15
|
+
archivePolicyBinding(path: string, source: string): { policySha256: string; archiveRoot: string };
|
|
16
|
+
ingestPolicySource(path: string, source: string, previous?: string, expectedPolicy?: string): { checkpointId: string; sourceStatus: string; issues?: string[]; [key: string]: unknown };
|
|
17
|
+
readArchiveCheckpoint(root: string, checkpoint: string): { checkpoint: { sourceManifestId: string; issues: string[]; [key: string]: unknown } };
|
|
18
|
+
readArchiveSource(root: string, id: string): { status: string; bytes?: Uint8Array; reference?: { retention: string; sha256: string; [key: string]: unknown } };
|
|
19
|
+
captureArchivedWorkSignals(root: string, id: string, context: WorkFrozen<WorkProjectionContext>, facts: unknown): { caseBatchId: string; observationId: string; linkageManifestId: string };
|
|
20
|
+
readRetainedExecution(root: string, id: string): { projection: unknown };
|
|
21
|
+
projectRetainedExecutions(values: readonly unknown[]): unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface DashboardHostConfig {
|
|
24
|
+
version: "producer-dashboard-host-v1"; trustDirectory: string; trustPolicyId: string; archiveRoot: string; scope: string; author: string;
|
|
25
|
+
policyPath: string; policySha256: string; sources: { id: string; kind: "work" | "retention" | "facts" }[];
|
|
26
|
+
selection: WorkProjectionContext["selectedSnapshot"]; cases: CaseSelection | null; blind: DurableBlindBinding | null;
|
|
27
|
+
budgetDigest: string; experimentDigest: string | null; harnessArtifactDigest: string;
|
|
28
|
+
ordinaryDigest?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DashboardHostRequest { version: "1.0"; requestId: string; hostDigest: string; expectedTip: string; selectionDigest: string; operation: "observe" | "present" | "presented" | "defer" | "debrief" | "dispatch" | "intent" | "cancel" | "dispatch-reconcile" | "intent-reconcile" | "ordinary-cancel"; payload: unknown }
|
|
31
|
+
export interface DashboardHostAuthority {
|
|
32
|
+
hostDigests: readonly string[]; requestDigests: readonly string[];
|
|
33
|
+
workContext: WorkFrozen<WorkProjectionContext>; dispatch: DispatchAuthority | null; experiment: ExperimentAuthority | null;
|
|
34
|
+
ordinary?: OrdinaryAuthority | null;
|
|
35
|
+
}
|
|
36
|
+
export const dashboardHostDigest = (c: DashboardHostConfig): string => dataDigest(c);
|
|
37
|
+
export const dashboardSelectionDigest = (selection: DashboardHostConfig["selection"]): string => dataDigest(selection);
|
|
38
|
+
export function dashboardHostRequest(input: DashboardHostRequest) {
|
|
39
|
+
const r=detached(input);controlShape(r,["version","requestId","hostDigest","expectedTip","selectionDigest","operation","payload"]);
|
|
40
|
+
if(r.version!=="1.0"||!/^[a-zA-Z0-9:_-]{1,128}$/.test(r.requestId)||![r.hostDigest,r.expectedTip,r.selectionDigest].every(sha)||!["observe","present","presented","defer","debrief","dispatch","intent","cancel","dispatch-reconcile","intent-reconcile","ordinary-cancel"].includes(r.operation)||Buffer.byteLength(JSON.stringify(r))>60000)throw Error("bounded exact dashboard request required");return freeze(r);
|
|
41
|
+
}
|
|
42
|
+
export const dashboardHostRequestDigest = (r: DashboardHostRequest): string => dataDigest(dashboardHostRequest(r));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createServer, createConnection, type Socket } from "node:net";
|
|
2
|
+
import { lstat, realpath, chmod } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute } from "node:path";
|
|
4
|
+
import { parseRetentionJson } from "./retention-json.ts";
|
|
5
|
+
import { isDashboardHost, dashboardHostRequest, type DashboardHost, type DashboardHostRequest } from "./dashboard-host.ts";
|
|
6
|
+
export const ENV_DASHBOARD_HOST_SOCKET="PI_DADDY_HOST_SOCKET";
|
|
7
|
+
export type DashboardConnection={frame:DashboardHost["frame"];action:DashboardHost["action"]};
|
|
8
|
+
const connections=new WeakSet<object>();
|
|
9
|
+
export const isDashboardConnection=(v:unknown):v is DashboardConnection=>isDashboardHost(v)||typeof v==="object"&&v!==null&&connections.has(v);
|
|
10
|
+
async function parent(path:string){if(!isAbsolute(path)||Buffer.byteLength(path)>100||await realpath(dirname(path))!==dirname(path))throw Error("canonical bounded private dashboard socket required");const s=await lstat(dirname(path));if(!s.isDirectory()||s.mode&0o077||s.uid!==process.getuid?.())throw Error("private owned dashboard socket parent required");}
|
|
11
|
+
/** Same original host process. No worker socket, native TUI attach, PID lookup or authority transport. */
|
|
12
|
+
export async function serveDashboardHost(path:string,host:DashboardHost){
|
|
13
|
+
if(!isDashboardHost(host))throw Error("original dashboard host required");await parent(path);const clients=new Set<Socket>(),tasks=new Set<Promise<void>>();
|
|
14
|
+
const server=createServer(socket=>{if(clients.size>=4){socket.destroy();return;}clients.add(socket);socket.on("close",()=>clients.delete(socket));socket.on("error",()=>{});socket.setTimeout(5000,()=>socket.destroy());let size=0,used=false;const chunks:Buffer[]=[];
|
|
15
|
+
socket.on("data",bytes=>{if(used)return;size+=bytes.length;if(size>65536){socket.destroy();return;}chunks.push(bytes);if(bytes.at(-1)!==10)return;used=true;
|
|
16
|
+
const task=(async()=>{try{const text=new TextDecoder("utf-8",{fatal:true}).decode(Buffer.concat(chunks));const r=parseRetentionJson(text,65536) as {operation:string;request?:DashboardHostRequest};let result:unknown;
|
|
17
|
+
if(Object.keys(r).join()==="operation"&&r.operation==="frame")result=await host.frame();
|
|
18
|
+
else if(Object.keys(r).sort().join()==="operation,request"&&r.operation==="action")result=await host.action(dashboardHostRequest(r.request!));else throw Error("closed dashboard protocol required");
|
|
19
|
+
const output=JSON.stringify({ok:true,result})+"\n";if(Buffer.byteLength(output)>512*1024)throw Error("dashboard frame bound exceeded");socket.end(output);
|
|
20
|
+
}catch{socket.end(JSON.stringify({ok:false,error:"refused or acknowledgement unknown; explicit readback only"})+"\n");}})();tasks.add(task);void task.finally(()=>tasks.delete(task));
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
await new Promise<void>((resolve,reject)=>{server.once("error",reject);server.listen(path,()=>{server.off("error",reject);resolve();});});
|
|
24
|
+
try{await chmod(path,0o600);}catch(error){server.close();throw error;}
|
|
25
|
+
return Object.freeze({path,close:()=>new Promise<void>((resolve,reject)=>{for(const c of clients)c.destroy();server.close(e=>e?reject(e):void Promise.allSettled([...tasks]).then(()=>resolve()));})});
|
|
26
|
+
}
|
|
27
|
+
/** Explicit operator-selected endpoint. Socket ownership is access policy, not module/human authentication. */
|
|
28
|
+
export function connectDashboardHost(path:string):DashboardConnection{
|
|
29
|
+
const call=async(operation:string,request?:DashboardHostRequest)=>{await parent(path);const s=await lstat(path);if(!s.isSocket()||s.mode&0o077||s.uid!==process.getuid?.())throw Error("private original dashboard endpoint unavailable");
|
|
30
|
+
return new Promise<any>((resolve,reject)=>{const socket=createConnection(path);let size=0,done=false;const chunks:Buffer[]=[];const fail=(e:unknown)=>{if(!done){done=true;socket.destroy();reject(e);}};
|
|
31
|
+
socket.setTimeout(5000,()=>fail(Error("dashboard acknowledgement timeout; do not repeat action")));socket.on("error",fail);socket.on("connect",()=>socket.write(JSON.stringify({operation,...(request?{request}:{})})+"\n"));
|
|
32
|
+
socket.on("data",b=>{size+=b.length;if(size>512*1024){fail(Error("dashboard frame limit"));return;}chunks.push(b);});
|
|
33
|
+
socket.on("end",()=>{try{const text=new TextDecoder("utf-8",{fatal:true}).decode(Buffer.concat(chunks));const r=parseRetentionJson(text,512*1024) as {ok:boolean;result:unknown};if(r.ok!==true)throw Error("dashboard refused or acknowledgement unknown");if(operation==="frame"&&(r.result as {version?:string})?.version!=="producer-dashboard-frame-v1")throw Error("incompatible dashboard host");if(!done){done=true;resolve(r.result);}}catch(e){fail(e);}});
|
|
34
|
+
socket.on("close",()=>{if(!done)fail(Error("dashboard disconnected; acknowledgement unknown"));});
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const api={frame:()=>call("frame"),action:(r:DashboardHostRequest)=>call("action",dashboardHostRequest(r))};connections.add(api);return Object.freeze(api);
|
|
38
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { isOrdinaryChildren, holdOrdinaryDispatch, type OrdinaryChildren, type OrdinaryCancellation } from "./ordinary-children.ts";
|
|
2
|
+
import { loadedDashboardHarnessDigest } from "./dashboard-harness.ts";
|
|
3
|
+
import { join, isAbsolute } from "node:path";
|
|
4
|
+
import { dataDigest, detached, freeze, sha, reviewPage } from "./debrief-contract.ts";
|
|
5
|
+
import { createRetainedDebrief } from "./debrief-host.ts";
|
|
6
|
+
import type { DebriefCheckpoint, DebriefPresenter, DebriefPersistence } from "./debrief.ts";
|
|
7
|
+
import { debriefAction } from "./debrief-render.ts";
|
|
8
|
+
import { openResourceBudget, resourceBindingDigest, type GovernedBudgetBinding } from "./resource-budget.ts";
|
|
9
|
+
import { dispatchRequest, dispatchRequestDigest, controlShape, type DispatchRequest } from "./dispatch-control.ts";
|
|
10
|
+
import { intentRequest, intentRequestDigest, type IntentRequest } from "./intent-control.ts";
|
|
11
|
+
import { experimentBindingDigest, isExperimentController, type ExperimentCancellation, type openExperiment } from "./experiment.ts";
|
|
12
|
+
import { dashboardObservations } from "./dashboard-observation.ts";
|
|
13
|
+
import { dashboardHostDigest, dashboardHostRequest, dashboardHostRequestDigest, dashboardSelectionDigest,
|
|
14
|
+
type DashboardHarness, type DashboardHostConfig, type DashboardHostRequest, type DashboardHostAuthority } from "./dashboard-host-contract.ts";
|
|
15
|
+
export * from "./dashboard-host-contract.ts";
|
|
16
|
+
const hosts=new WeakSet<object>();
|
|
17
|
+
export const isDashboardHost=(value:unknown):value is DashboardHost=>typeof value==="object"&&value!==null&&hosts.has(value);
|
|
18
|
+
export type DashboardHost=ReturnType<typeof openDashboardHost>;
|
|
19
|
+
export interface DashboardHostOptions { ordinary?:OrdinaryChildren; harness:DashboardHarness; config:DashboardHostConfig; budget:GovernedBudgetBinding; experiment?:ReturnType<typeof openExperiment>; authority:()=>DashboardHostAuthority|null;
|
|
20
|
+
/** Independently sourced current declaration. Never inferred from idle, a PID, title or request fields. */
|
|
21
|
+
presence?:()=>{present:boolean;closing:boolean;evidenceDigest:string;expiresAt:number}|null;
|
|
22
|
+
}
|
|
23
|
+
function configuration(input:DashboardHostConfig){
|
|
24
|
+
const c=detached(input);controlShape(c,["version","trustDirectory","trustPolicyId","archiveRoot","scope","author","policyPath","policySha256","sources","selection","cases","blind","budgetDigest","experimentDigest","harnessArtifactDigest",...(Object.hasOwn(c,"ordinaryDigest")?["ordinaryDigest"]:[])]);
|
|
25
|
+
if(Object.hasOwn(c,"ordinaryDigest")&&!sha(c.ordinaryDigest))throw Error("invalid ordinary binding digest");
|
|
26
|
+
if(c.version!=="producer-dashboard-host-v1"||![c.trustDirectory,c.archiveRoot,c.policyPath].every(p=>typeof p==="string"&&isAbsolute(p))||![c.trustPolicyId,c.policySha256,c.budgetDigest,c.harnessArtifactDigest].every(sha)||!(c.experimentDigest===null||sha(c.experimentDigest))||typeof c.scope!=="string"||!c.scope||c.scope.length>128||typeof c.author!=="string"||!c.author||c.author.length>256||!Array.isArray(c.sources)||c.sources.length<1||c.sources.length>32||new Set(c.sources.map(s=>s.id)).size!==c.sources.length||c.sources.filter(s=>s.kind==="work").length>1||c.sources.some(s=>!/^[a-zA-Z0-9:_-]{1,128}$/.test(s.id)||!["work","retention","facts"].includes(s.kind)))throw Error("invalid explicit dashboard host binding");
|
|
27
|
+
return freeze(c);
|
|
28
|
+
}
|
|
29
|
+
/** Explicit creation, NEVER called by refresh/reconnect. One fixed host child per registered trust store. */
|
|
30
|
+
export function createDashboardHost(options:DashboardHostOptions){
|
|
31
|
+
const c=configuration(options.config),h=options.harness,a=options.authority();
|
|
32
|
+
if(loadedDashboardHarnessDigest(h)!==c.harnessArtifactDigest)throw Error("verified loaded harness artifact required");
|
|
33
|
+
if(!a?.hostDigests.includes(dashboardHostDigest(c)))throw Error("independent exact host authority required");
|
|
34
|
+
const trust=h.openTrustLifecycle(c.trustDirectory),view=trust.inspect(Date.now());
|
|
35
|
+
if(view.policyId!==c.trustPolicyId)throw Error("trust policy changed");
|
|
36
|
+
h.learningJournal(join(c.trustDirectory,"producer-host"),{type:"producer-host-initial",config:c});
|
|
37
|
+
return openDashboardHost(options);
|
|
38
|
+
}
|
|
39
|
+
/** Existing native writers + original controllers; no model, launch, authority-file loader or PID recovery. */
|
|
40
|
+
export function openDashboardHost(options:DashboardHostOptions){
|
|
41
|
+
const c=configuration(options.config),h=options.harness,hostDigest=dashboardHostDigest(c),selectionDigest=dashboardSelectionDigest(c.selection);
|
|
42
|
+
if(loadedDashboardHarnessDigest(h)!==c.harnessArtifactDigest)throw Error("verified loaded harness artifact required");
|
|
43
|
+
const budget=openResourceBudget(options.budget),experiment=options.experiment,ordinary=options.ordinary;
|
|
44
|
+
if(ordinary&&(!isOrdinaryChildren(ordinary)||ordinary.bindingDigest!==c.ordinaryDigest))throw Error("original ordinary controller binding required");
|
|
45
|
+
if(resourceBindingDigest(budget.binding)!==c.budgetDigest||experiment&&(!isExperimentController(experiment)||experimentBindingDigest(experiment.binding)!==c.experimentDigest))throw Error("original controller binding required");
|
|
46
|
+
const trust=h.openTrustLifecycle(c.trustDirectory),journal=h.learningJournal(join(c.trustDirectory,"producer-host"));
|
|
47
|
+
const initial=journal.read()[0].value;
|
|
48
|
+
if(initial.type!=="producer-host-initial"||dataDigest(initial.config)!==hostDigest)throw Error("host store binding changed; no new budget");
|
|
49
|
+
const trustInput=trust.history()[0].value.input as {archiveRoot:string;component:unknown;exposure:{attentionRemaining:number}|null};
|
|
50
|
+
if(trustInput.archiveRoot!==c.archiveRoot||trust.inspect(Date.now()).policyId!==c.trustPolicyId||trustInput.exposure&&trustInput.exposure.attentionRemaining>5)throw Error("one scoped five-total attention policy required");
|
|
51
|
+
const authority=()=>{const a=options.authority();if(!a)return null;const safe=detached(a);if(!safe.hostDigests.includes(hostDigest))return null;return safe;};
|
|
52
|
+
const observation=dashboardObservations(h,c,journal.read),history=()=>journal.read();
|
|
53
|
+
const append=(value:Record<string,unknown>)=>journal.append(history().at(-1)!.id,value);
|
|
54
|
+
const saved=():DebriefCheckpoint|null=>{const rows=history().filter(e=>e.value.type==="checkpoint");return rows.length?detached(rows.at(-1)!.value.checkpoint) as DebriefCheckpoint:null;};
|
|
55
|
+
let presenter:DebriefPresenter|undefined,busy=false,poisoned=false,presentationRevision:number|null=null,presenceDigest:string|null=null,attentionDeferred:string|null=null;
|
|
56
|
+
const presence=()=>{const p=options.presence?.();return p&&p.present===true&&p.closing===true&&sha(p.evidenceDigest)&&Number.isFinite(p.expiresAt)&&p.expiresAt>Date.now()?p:null;};
|
|
57
|
+
const persistence:DebriefPersistence={durability:"host-owned",load:saved,compareAndSwap(expected,next){
|
|
58
|
+
const rows=history(),previous=saved();if((previous?dataDigest(previous):null)!==expected)throw Error("stale attention CAS");
|
|
59
|
+
if(previous&&(dataDigest(previous.caseIds)!==dataDigest(next.caseIds)||previous.blindDigest!==next.blindDigest))throw Error("no replacement cards or budget refill");
|
|
60
|
+
const cards=[...next.caseIds,...(next.blindDigest?[next.blindDigest]:[])];if(cards.length>5)throw Error("five total cards");
|
|
61
|
+
if(!previous)for(const card of cards){const result=trust.expose("producer:"+dataDigest({hostDigest,card}),Date.now());if(result.mode!=="ask"){attentionDeferred=result.reason;throw Error("predeclared policy defers this question: "+result.reason);}}
|
|
62
|
+
journal.append(rows.at(-1)!.id,{type:"checkpoint",checkpoint:next});
|
|
63
|
+
}};
|
|
64
|
+
const boundary=async()=>{const dispatch=await budget.controls(null).inspect(),accounting=await budget.inspect();return {dispatch,active:accounting.active};};
|
|
65
|
+
const paused=async(revision:number)=>{const b=await boundary();if(ordinary&&!ordinary.quiescent())throw Error("original ordinary children not quiescent");if(experiment){const view=await experiment.inspect();if(view.budget?.active!==0||view.control!=="not-assessed")throw Error("original experiment not quiescent");}if(!b.dispatch.paused||b.dispatch.admission!=="paused"||b.dispatch.revision!==revision||b.active!==0)throw Error("safe paused selection unavailable");};
|
|
66
|
+
const currentSelection=async()=>budget.binding.intent?(await budget.intentControls(null).inspect()).selection:c.selection;
|
|
67
|
+
const nativeRead=async()=>({dispatch:await budget.controls(null).inspect(),intent:budget.binding.intent?await budget.intentControls(null).inspect():null,experiment:experiment?await experiment.inspect():null,ordinary:ordinary?ordinary.inspect():null});
|
|
68
|
+
const api={hostDigest,selectionDigest,
|
|
69
|
+
async frame(){
|
|
70
|
+
const rows=history(),tip=rows.at(-1)!.id,a=authority();let source:unknown=null,error:string|null=null;
|
|
71
|
+
let selection=c.selection,selectionState="current-control-snapshot";
|
|
72
|
+
try{selection=await currentSelection();}catch(e){error=String(e);selectionState="unavailable-host-declaration-only";}
|
|
73
|
+
try{source=await observation.view(selectionState==="current-control-snapshot"?a:null,selection);}catch(e){error=String(e);}
|
|
74
|
+
let debrief:unknown=null;
|
|
75
|
+
if(a&&presenter&&presentationRevision!==null&&presence()?.evidenceDigest===presenceDigest){try{await paused(presentationRevision);debrief=presenter.view();}catch{debrief=null;}}
|
|
76
|
+
let controls:unknown=null;try{controls=await nativeRead();}catch(e){error=String(e);}
|
|
77
|
+
const attention=trust.inspect(Date.now());
|
|
78
|
+
return freeze(detached({version:"producer-dashboard-frame-v1",hostDigest,selectionDigest:dashboardSelectionDigest(selection),selectionState,tip,source,controls,debrief,attention,error,
|
|
79
|
+
requests:rows.filter(e=>["claim","result","presentation","defer","ordinary-intent-pending"].includes(String(e.value.type))).map(e=>e.value),
|
|
80
|
+
control:rows.some(e=>e.value.type==="host-failure")?"failed":rows.some(e=>e.value.type==="claim"&&!rows.some(r=>r.value.type==="result"&&r.value.requestId===e.value.requestId))?"unknown":"not-assessed",
|
|
81
|
+
acknowledgement:poisoned?"unknown":"readback-only",identity:"independently-declared-host; not human/module authentication",activeBranch:null,acceptance:"not-assessed",freshness:"snapshot-unknown",workerInteractions:0}));
|
|
82
|
+
},
|
|
83
|
+
/** Explicit reconciliation is read-only. A retained claim is never replayed as an effect. */
|
|
84
|
+
reconcile:()=>api.frame(),
|
|
85
|
+
async action(input:DashboardHostRequest){
|
|
86
|
+
const request=dashboardHostRequest(input),digest=dashboardHostRequestDigest(request);
|
|
87
|
+
if(busy||poisoned)throw Error("dashboard operation busy or acknowledgement unknown");busy=true;let attempted=false;
|
|
88
|
+
try{
|
|
89
|
+
const rows=history(),old=rows.find(e=>e.value.type==="claim"&&e.value.requestId===request.requestId);
|
|
90
|
+
if(old){if(old.value.digest!==digest)throw Error("immutable dashboard request ID");return {state:rows.some(e=>e.value.type==="host-failure")?"failed-or-unknown":"readback-only",result:rows.find(e=>e.value.type==="result"&&e.value.requestId===request.requestId)?.value??null};}
|
|
91
|
+
const selection=request.operation==="observe"?await currentSelection().catch(()=>c.selection):await currentSelection();
|
|
92
|
+
if(request.hostDigest!==hostDigest||request.selectionDigest!==dashboardSelectionDigest(selection)||request.expectedTip!==rows.at(-1)!.id)throw Error("stale dashboard selection/CAS");
|
|
93
|
+
if(rows.some(e=>e.value.type==="host-failure")||rows.some(e=>e.value.type==="claim"&&!rows.some(r=>r.value.type==="result"&&r.value.requestId===e.value.requestId)))throw Error("prior acknowledgement unknown; inspect without repeating effects");
|
|
94
|
+
const a=authority(),approved=Boolean(a?.requestDigests.includes(digest));
|
|
95
|
+
attempted=true;journal.append(request.expectedTip,{type:"claim",requestId:request.requestId,digest,request,decision:approved?"approved":"denied"});
|
|
96
|
+
if(!approved){append({type:"result",requestId:request.requestId,state:"denied"});return {state:"denied"};}
|
|
97
|
+
let result:unknown,releaseOrdinary:(()=>void)|undefined;
|
|
98
|
+
try{
|
|
99
|
+
if(request.operation==="observe"){
|
|
100
|
+
const captured=observation.observe(request.payload,a!,selection);append({type:"observation",observation:captured});result=captured;
|
|
101
|
+
}else if(request.operation==="present"){
|
|
102
|
+
const p=detached(request.payload) as {userPresent:boolean;closing:boolean;evidenceDigest:string;dispatchRevision:number};
|
|
103
|
+
if(Object.keys(p).sort().join()!=="closing,dispatchRevision,evidenceDigest,userPresent"||!sha(p.evidenceDigest)||typeof p.userPresent!=="boolean"||typeof p.closing!=="boolean"||!Number.isSafeInteger(p.dispatchRevision))throw Error("explicit presence/boundary evidence required");
|
|
104
|
+
if(!p.userPresent||!p.closing||presence()?.evidenceDigest!==p.evidenceDigest){append({type:"defer",requestId:request.requestId,evidence:p,reason:"absent or uncertain closing boundary",weekly:true});presenter?.close();presentationRevision=null;result={state:"deferred"};}
|
|
105
|
+
else{
|
|
106
|
+
await paused(p.dispatchRevision);const cases=observation.latest().find(r=>r.kind==="work")?.cases??c.cases;if(!cases)throw Error("retained selected case batch unavailable");
|
|
107
|
+
const reviewer=cases.version==="work-signals-v1"?h.createWorkSignalReviewer(c.archiveRoot,cases.batchId,c.author):h.createWorkCaseReviewer(c.archiveRoot,cases.batchId,c.author);
|
|
108
|
+
const page=reviewPage(await reviewer.list(0,c.blind?4:5),c.blind?4:5,0,cases.version==="work-signals-v1"?{batchId:cases.batchId,observationId:cases.observationId}:undefined);
|
|
109
|
+
if(page.items.some(i=>dataDigest({kind:"detector",...i.candidate.detector as object})!==dataDigest(trustInput.component)))throw Error("case population/version differs from predeclared trust policy");
|
|
110
|
+
presenter=createRetainedDebrief(h,{archiveRoot:c.archiveRoot,scope:c.scope,author:c.author,cases,blind:c.blind??undefined,persistence});
|
|
111
|
+
attentionDeferred=null;const view=await presenter.open({mode:"manual",userPresent:true});await paused(p.dispatchRevision);
|
|
112
|
+
if(attentionDeferred){presenter.close();presentationRevision=null;append({type:"defer",requestId:request.requestId,reason:attentionDeferred,weekly:true});result={state:"deferred",reason:attentionDeferred};}
|
|
113
|
+
else {if(view.state!=="open")throw Error("presentation data/attention acknowledgement unavailable");
|
|
114
|
+
presentationRevision=p.dispatchRevision;presenceDigest=p.evidenceDigest;const frameDigest=dataDigest(view);append({type:"presentation",requestId:request.requestId,evidence:p,frameDigest,state:"prepared-not-delivery",automatic:"unqualified"});result={state:"prepared",frameDigest};}
|
|
115
|
+
}
|
|
116
|
+
}else if(request.operation==="presented"){
|
|
117
|
+
const p=detached(request.payload) as {frameDigest:string};if(Object.keys(p).join()!=="frameDigest"||!presenter||presentationRevision===null)throw Error("original prepared presentation required");
|
|
118
|
+
await paused(presentationRevision);if(presence()?.evidenceDigest!==presenceDigest)throw Error("user absent/presence changed");if(p.frameDigest!==dataDigest(presenter.view()))throw Error("stale presentation acknowledgement");result={state:"host-acknowledged-presentation",frameDigest:p.frameDigest,humanAuthentication:"unqualified"};
|
|
119
|
+
}else if(request.operation==="defer"){
|
|
120
|
+
presenter?.close();presentationRevision=null;append({type:"defer",requestId:request.requestId,weekly:true,evidence:request.payload});result={state:"deferred"};
|
|
121
|
+
}else if(request.operation==="debrief"){
|
|
122
|
+
if(!presenter||presentationRevision===null||typeof request.payload!=="string")throw Error("original paused presenter required");await paused(presentationRevision);if(presence()?.evidenceDigest!==presenceDigest)throw Error("user absent/presence changed");result=await debriefAction(presenter,request.payload);await paused(presentationRevision);if(result==="unknown"||result==="stale-or-unknown")throw Error("debrief writer acknowledgement unknown");
|
|
123
|
+
}else{
|
|
124
|
+
presenter?.close();presentationRevision=null;
|
|
125
|
+
if(request.operation==="dispatch"||request.operation==="dispatch-reconcile"){
|
|
126
|
+
const native=dispatchRequest(request.payload as DispatchRequest);if(native.action==="cancel-execution")throw Error("cancellation needs exact separately approved native bridge");
|
|
127
|
+
const port=budget.controls(a!.dispatch);
|
|
128
|
+
if(request.operation==="dispatch-reconcile"&&(await port.inspect()).records.find(r=>r.request.requestId===native.requestId)?.digest!==dispatchRequestDigest(native))throw Error("exact original dispatch reconciliation required");
|
|
129
|
+
result=request.operation==="dispatch"?await port.request(native):await port.reconcile(native.requestId);
|
|
130
|
+
}else if(request.operation==="intent"||request.operation==="intent-reconcile"){
|
|
131
|
+
const native=detached(intentRequest(request.payload as IntentRequest)) as IntentRequest,port=budget.intentControls(a!.dispatch),digest=intentRequestDigest(native);
|
|
132
|
+
if(c.ordinaryDigest&&!ordinary)throw Error("original ordinary boundary unavailable; no recovery");
|
|
133
|
+
if(!ordinary)result=request.operation==="intent"?await port.request(native):await port.reconcile(native);
|
|
134
|
+
else {
|
|
135
|
+
const pending=history().filter(e=>e.value.type==="ordinary-intent-pending"&&e.value.nativeRequestId===native.requestId);
|
|
136
|
+
if(pending.some(e=>e.value.digest!==digest))throw Error("immutable pending ordinary intent ID");
|
|
137
|
+
if(pending.some(e=>!history().some(row=>{const original=row.value.request as DashboardHostRequest|undefined;return row.value.type==="claim"&&row.value.requestId===e.value.hostRequestId&&row.value.decision==="approved"&&original&&["intent","intent-reconcile"].includes(original.operation)&&intentRequestDigest(original.payload as IntentRequest)===digest;})))throw Error("unbound ordinary pending transport");
|
|
138
|
+
const exists=(await port.inspect()).records.some(r=>r.requestId===native.requestId);
|
|
139
|
+
if(request.operation==="intent-reconcile"&&!exists&&!pending.length)throw Error("original pending intent required for reconciliation");
|
|
140
|
+
if(ordinary&&(a!.dispatch?.authorityDigest!==budget.binding.authorityDigest||!a!.dispatch.requestDigests.includes(digest)))throw Error("independent native intent authority required before ordinary hold");
|
|
141
|
+
const held=ordinary?holdOrdinaryDispatch(ordinary,dataDigest({hostDigest,digest})):undefined;
|
|
142
|
+
if(held&&!held.ready()){
|
|
143
|
+
if(!pending.length)append({type:"ordinary-intent-pending",nativeRequestId:native.requestId,digest,hostRequestId:request.requestId});
|
|
144
|
+
result={state:"pending-ordinary-boundary",nativeRequestId:native.requestId,application:"not-acknowledged"};
|
|
145
|
+
}else{
|
|
146
|
+
result=(request.operation==="intent"||pending.length>0&&!exists)?await port.request(native):await port.reconcile(native);
|
|
147
|
+
const outcome=(result as {records:{requestId:string;application:string}[]}).records.find(r=>r.requestId===native.requestId);
|
|
148
|
+
if(held&&!outcome)throw Error("native intent application acknowledgement unavailable");
|
|
149
|
+
if(held&&["applied","not-applied"].includes(outcome!.application))releaseOrdinary=held.release;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}else if(request.operation==="ordinary-cancel"){
|
|
153
|
+
if(!ordinary||!isOrdinaryChildren(ordinary))throw Error("original ordinary child handles unavailable; no recovery");
|
|
154
|
+
result=ordinary.cancel(request.payload as OrdinaryCancellation,a!.ordinary??null);
|
|
155
|
+
}else if(request.operation==="cancel"){
|
|
156
|
+
const p=detached(request.payload) as {dispatch:DispatchRequest;cancellation:ExperimentCancellation};controlShape(p,["dispatch","cancellation"]);const legacy=dispatchRequest(p.dispatch);
|
|
157
|
+
const current=await budget.controls(null).inspect();
|
|
158
|
+
if(!experiment||!isExperimentController(experiment)||legacy.action!=="cancel-execution"||legacy.bindingDigest!==c.budgetDigest||legacy.expectedRevision!==current.revision||legacy.targetExecutionId!==p.cancellation.executionId||legacy.requestId!==p.cancellation.requestId)throw Error("exact original cancellation target/revision required");
|
|
159
|
+
if(a!.dispatch?.authorityDigest!==budget.binding.authorityDigest||!a!.dispatch.requestDigests.includes(dispatchRequestDigest(legacy)))throw Error("independent legacy cancellation authority required");
|
|
160
|
+
result=await experiment.cancel(p.cancellation,a!.experiment);
|
|
161
|
+
}else throw Error("unsupported dashboard operation");
|
|
162
|
+
}
|
|
163
|
+
}catch(error){presenter?.close();presentationRevision=null;append({type:"result",requestId:request.requestId,state:"failed-or-unknown",reason:String(error)});throw error;}
|
|
164
|
+
append({type:"result",requestId:request.requestId,state:"acknowledged",resultDigest:dataDigest(result??null)});releaseOrdinary?.();return {state:"acknowledged",result};
|
|
165
|
+
}catch(error){if(attempted){poisoned=true;try{if(history().some(e=>e.value.type==="claim"&&e.value.requestId===request.requestId))append({type:"host-failure",requestId:request.requestId,reason:String(error)});}catch{/* Failure recording also unacknowledged; no claim of no effects. */}}throw error;}finally{busy=false;}
|
|
166
|
+
}
|
|
167
|
+
};hosts.add(api);return Object.freeze(api);
|
|
168
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { parseRetentionJson } from "./retention-json.ts";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { createDailyViewReader } from "./daily-view.ts";
|
|
4
|
+
import { dataDigest, detached } from "./debrief-contract.ts";
|
|
5
|
+
import type { DashboardHarness, DashboardHostConfig, DashboardHostAuthority, HostEvent } from "./dashboard-host-contract.ts";
|
|
6
|
+
export interface DashboardObservation { sourceId: string; kind: "work" | "retention" | "facts"; checkpointId: string; sourceManifestId: string | null; metadata: unknown; cases: { version: "work-signals-v1"; batchId: string; observationId: string } | null; linkageManifestId: string | null }
|
|
7
|
+
export function dashboardObservations(h: DashboardHarness, c: DashboardHostConfig, history: () => HostEvent[]) {
|
|
8
|
+
const reader=createDailyViewReader(), root=c.archiveRoot;
|
|
9
|
+
const latest=()=>{const rows=new Map<string,DashboardObservation>();for(const e of history())if(e.value.type==="observation") {const row=e.value.observation as unknown as DashboardObservation;rows.set(row.sourceId,row);}return [...rows.values()];};
|
|
10
|
+
const policy=(id:string)=>{const binding=h.archivePolicyBinding(c.policyPath,id);if(binding.archiveRoot!==root||binding.policySha256!==c.policySha256)throw Error("source policy binding changed");};
|
|
11
|
+
const bytes=(id:string)=>{const source=h.readArchiveSource(root,id);if(source.status!=="available"||source.reference?.retention!=="exact"||!source.bytes||createHash("sha256").update(source.bytes).digest("hex")!==source.reference.sha256)throw Error("exact retained source unavailable");return Uint8Array.from(source.bytes);};
|
|
12
|
+
return { latest,
|
|
13
|
+
observe(payload: unknown, authority: DashboardHostAuthority, selection: DashboardHostConfig["selection"]): DashboardObservation {
|
|
14
|
+
const input=detached(payload) as {sourceId:string;previousCheckpointId:string|null;facts:unknown};
|
|
15
|
+
if(Object.keys(input).sort().join()!=="facts,previousCheckpointId,sourceId")throw Error("exact observation request required");
|
|
16
|
+
const source=c.sources.find(s=>s.id===input.sourceId);if(!source)throw Error("source outside host binding");policy(source.id);
|
|
17
|
+
const previous=latest().find(r=>r.sourceId===source.id)?.checkpointId??null;if(input.previousCheckpointId!==previous)throw Error("stale observation checkpoint");
|
|
18
|
+
const captured=h.ingestPolicySource(c.policyPath,source.id,previous??undefined,c.policySha256);policy(source.id);
|
|
19
|
+
let sourceManifestId:string|null=null,cases:DashboardObservation["cases"]=null,linkageManifestId:string|null=null;
|
|
20
|
+
if(source.kind==="work"||source.kind==="facts"){
|
|
21
|
+
const checkpoint=h.readArchiveCheckpoint(root,captured.checkpointId).checkpoint;sourceManifestId=checkpoint.sourceManifestId;bytes(sourceManifestId);
|
|
22
|
+
if(source.kind==="facts") {if(input.facts!==null)throw Error("facts sources are declarations, not replacement authority");return {sourceId:source.id,kind:source.kind,checkpointId:captured.checkpointId,sourceManifestId,metadata:captured,cases:null,linkageManifestId:null};}
|
|
23
|
+
const factSource=latest().find(r=>r.kind==="facts"&&r.sourceManifestId===input.facts);if(!factSource)throw Error("exact observed fact source required, not caller replacement facts");
|
|
24
|
+
const facts=parseRetentionJson(new TextDecoder("utf-8",{fatal:true}).decode(bytes(factSource.sourceManifestId!)),256*1024);captured.factSourceManifestId=factSource.sourceManifestId;captured.factBasis="explicit-host-declarations-not-authenticated-truth";
|
|
25
|
+
if(dataDigest(authority.workContext.selectedSnapshot)!==dataDigest(selection))throw Error("independent source selection changed");
|
|
26
|
+
try { const signals=h.captureArchivedWorkSignals(root,sourceManifestId,authority.workContext,facts);
|
|
27
|
+
cases={version:"work-signals-v1",batchId:signals.caseBatchId,observationId:signals.observationId};linkageManifestId=signals.linkageManifestId;
|
|
28
|
+
} catch(error) { captured.semanticFailure=String(error); } // Retain checkpoint/gap even when nomination is unavailable.
|
|
29
|
+
} else {if(input.facts!==null||captured.kind!=="execution-retention-v2")throw Error("retention source requires exact referenced-blobs policy");h.readRetainedExecution(root,captured.checkpointId);}
|
|
30
|
+
return {sourceId:source.id,kind:source.kind,checkpointId:captured.checkpointId,sourceManifestId,metadata:captured,cases,linkageManifestId};
|
|
31
|
+
},
|
|
32
|
+
async view(authority: DashboardHostAuthority | null, selection: DashboardHostConfig["selection"]) {
|
|
33
|
+
const rows=latest(), projections:unknown[]=[], manifests:Record<string,Uint8Array>={};let workBytes:Uint8Array|undefined;
|
|
34
|
+
for(const row of rows){policy(row.sourceId);if(row.kind==="work")workBytes=bytes(row.sourceManifestId!);else if(row.kind==="retention"){
|
|
35
|
+
const read=h.readRetainedExecution(root,row.checkpointId);projections.push(read.projection);
|
|
36
|
+
const raw=JSON.parse(new TextDecoder().decode(bytes(row.checkpointId))),manifest=bytes(raw.manifestId);manifests[createHash("sha256").update(manifest).digest("hex")]=manifest;
|
|
37
|
+
}}
|
|
38
|
+
const context=authority?.workContext??{selectedSnapshot:selection,authority:null};
|
|
39
|
+
if(dataDigest(context.selectedSnapshot)!==dataDigest(selection))throw Error("dashboard selection drift");
|
|
40
|
+
const daily=await reader({workBytes,archiveBytes:Buffer.from(JSON.stringify(h.projectRetainedExecutions(projections))),sourceManifestBytes:manifests,workContext:context});
|
|
41
|
+
return {daily,observations:rows,coverage:"partial",unobservedSources:c.sources.filter(s=>!rows.some(r=>r.sourceId===s.id)).map(s=>s.id),sourceJobGaps:history().filter(e=>e.value.type==="claim"&&!history().some(r=>r.value.type==="result"&&r.value.requestId===e.value.requestId)).map(e=>String(e.value.requestId)),workerInteractions:0,continuity:"retained-checkpoints-not-live-continuity"};
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { Compile } from "typebox/compile";
|
|
4
|
+
import { parseRetentionJson } from "./retention-json.ts";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const caseCheck = Compile(require("../contracts/debrief/v1/work-capture/work-case.schema.json"));
|
|
7
|
+
const requestCheck = Compile(require("../contracts/debrief/v1/work-capture/review-request.schema.json"));
|
|
8
|
+
export type Disposition = "confirmed_defect" | "expected_behavior" | "exemplar" | "uncertain" | "skip";
|
|
9
|
+
export interface ReviewRequest { caseManifestId: string; priorDecisionId: string | null; disposition: Disposition; note: string }
|
|
10
|
+
export interface ReviewItem { caseManifestId: string; candidate: { id: string; reason: string; [key: string]: unknown }; priorDecisionId: string | null; disposition: Disposition | "unresolved" }
|
|
11
|
+
export interface ReviewPort {
|
|
12
|
+
list(offset: number, limit: number): unknown | Promise<unknown>;
|
|
13
|
+
history(caseManifestId: string): unknown | Promise<unknown>;
|
|
14
|
+
decide?: (request: ReviewRequest) => unknown | Promise<unknown>;
|
|
15
|
+
}
|
|
16
|
+
export interface BlindChoice { kind: "one" | "tie" | "none" | "insufficient"; labels: string[] }
|
|
17
|
+
export interface BlindView { version: "blind-intervention-view-v1"; cards: { label: string; artifactDigests: string[] }[]; limitations: string[] }
|
|
18
|
+
export interface BlindPort {
|
|
19
|
+
quality?(): unknown | Promise<unknown>;
|
|
20
|
+
view(): unknown | Promise<unknown>;
|
|
21
|
+
readArtifact(label: string, digest: string): Uint8Array | Promise<Uint8Array>;
|
|
22
|
+
choose(choice: BlindChoice): unknown | Promise<unknown>;
|
|
23
|
+
reveal(): unknown | Promise<unknown>;
|
|
24
|
+
}
|
|
25
|
+
export const dataKey = (v: unknown): string => Array.isArray(v) ? `[${v.map(dataKey).join(",")}]` : v !== null && typeof v === "object"
|
|
26
|
+
? `{${Object.keys(v).sort().map(k => JSON.stringify(k) + ":" + dataKey((v as Record<string, unknown>)[k])).join(",")}}` : JSON.stringify(v);
|
|
27
|
+
export const dataDigest = (v: unknown) => createHash("sha256").update(dataKey(v)).digest("hex");
|
|
28
|
+
export const byteDigest = (v: Uint8Array) => createHash("sha256").update(v).digest("hex");
|
|
29
|
+
export const sha = (v: unknown): v is string => typeof v === "string" && /^[a-f0-9]{64}$/.test(v);
|
|
30
|
+
export function detached<T>(input: T): T {
|
|
31
|
+
const active = new Set<object>(); let count = 0;
|
|
32
|
+
const inspect = (v: unknown, depth: number): void => {
|
|
33
|
+
if (++count > 50000 || depth > 32) throw new Error("debrief data bound exceeded");
|
|
34
|
+
if (v === null || typeof v === "string" || typeof v === "boolean" || typeof v === "number" && Number.isFinite(v)) return;
|
|
35
|
+
if (!v || typeof v !== "object" || active.has(v)) throw new Error("plain debrief data required");
|
|
36
|
+
const array = Array.isArray(v), prototype = Object.getPrototypeOf(v), fields = Object.getOwnPropertyDescriptors(v);
|
|
37
|
+
if (array ? prototype !== Array.prototype || v.length > 4096 || Reflect.ownKeys(fields).length !== v.length + 1 : ![Object.prototype, null].includes(prototype)) throw new Error("plain debrief object/array required");
|
|
38
|
+
active.add(v);
|
|
39
|
+
for (const k of Reflect.ownKeys(fields)) {
|
|
40
|
+
if (array && k === "length") continue;
|
|
41
|
+
const d = fields[k as string]; if (typeof k !== "string" || !d.enumerable || !Object.hasOwn(d, "value")) throw new Error("hidden/accessor debrief data refused");
|
|
42
|
+
inspect(d.value, depth + 1);
|
|
43
|
+
}
|
|
44
|
+
if (array) for (let i = 0; i < v.length; i++) if (!Object.hasOwn(fields, String(i))) throw new Error("sparse debrief array");
|
|
45
|
+
active.delete(v);
|
|
46
|
+
};
|
|
47
|
+
inspect(input, 0);
|
|
48
|
+
const text = JSON.stringify(input);
|
|
49
|
+
if (!text || Buffer.byteLength(text) > 1024 * 1024) throw new Error("bounded debrief data required");
|
|
50
|
+
return parseRetentionJson(text) as T;
|
|
51
|
+
}
|
|
52
|
+
export function freeze<T>(v: T): T { if (v && typeof v === "object") { Object.values(v).forEach(freeze); Object.freeze(v); } return v; }
|
|
53
|
+
export function closed(v: unknown, names: string[]): asserts v is Record<string, unknown> {
|
|
54
|
+
if (!v || typeof v !== "object" || Array.isArray(v) || Object.keys(v).sort().join() !== [...names].sort().join()) throw new Error("closed debrief object required");
|
|
55
|
+
}
|
|
56
|
+
export function reviewRequest(input: ReviewRequest): ReviewRequest {
|
|
57
|
+
const r = detached(input); if (!requestCheck.Check(r)) throw new Error("invalid label request"); return freeze(r);
|
|
58
|
+
}
|
|
59
|
+
export interface SignalSelection { batchId: string; observationId: string }
|
|
60
|
+
function signalCase(c: Record<string, any>): boolean {
|
|
61
|
+
if (c.capture_schema === 2) return c.reason === "coverage_gap" && c.classification === "coverage_issue" && caseCheck.Check(c) && c.id === dataDigest({ detector: c.detector, target: c.target, reason: c.reason });
|
|
62
|
+
if (c.capture_schema !== 3 || c.classification !== "candidate_defect" || !["overdue_checkpoint", "reopened_acceptance", "intent_conflict"].includes(c.reason)) return false;
|
|
63
|
+
// Signal facts may contribute256 references plus the two base scope/obligation references.
|
|
64
|
+
if (!Array.isArray(c.evidence) || !c.evidence.length || c.evidence.length > 258 || !c.evidence.every(sha)) return false;
|
|
65
|
+
const common = { ...c, capture_schema: 2, reason: "coverage_gap", classification: "coverage_issue", metrics: {}, evidence: c.evidence.slice(0, 256) };
|
|
66
|
+
if (!caseCheck.Check(common) || c.detector.id !== c.reason || !c.detector.version.startsWith("work-signals-v1:") || !c.metrics || typeof c.metrics !== "object") return false;
|
|
67
|
+
const keys = c.reason === "overdue_checkpoint" ? ["deadlineMs", "observedAt"] : c.reason === "intent_conflict" ? ["failedChecks"] : [];
|
|
68
|
+
closed(c.metrics, keys);
|
|
69
|
+
if (keys.some(k => !Number.isSafeInteger(c.metrics[k]) || c.metrics[k] < 0)) return false;
|
|
70
|
+
const m = c.metrics as Record<string, number>;
|
|
71
|
+
if (c.reason === "overdue_checkpoint" && m.observedAt <= m.deadlineMs || c.reason === "intent_conflict" && m.failedChecks < 1) return false;
|
|
72
|
+
const detector = { id: c.detector.id, version: c.detector.version, population: c.detector.population };
|
|
73
|
+
const target = { kind: c.target.kind, snapshotDigest: c.target.snapshotDigest, obligationId: c.target.obligationId, obligationDigest: c.target.obligationDigest };
|
|
74
|
+
const metrics = Object.fromEntries(keys.map(k => [k, c.metrics[k]])), evidence = [...new Set(c.evidence)].sort();
|
|
75
|
+
return dataKey(evidence) === dataKey(c.evidence) && c.id === createHash("sha256").update(JSON.stringify({ capture_schema: 3, detector, target, reason: c.reason, metrics, evidence })).digest("hex");
|
|
76
|
+
}
|
|
77
|
+
export function reviewPage(value: unknown, limit: number, offset = 0, signals?: SignalSelection) {
|
|
78
|
+
const page = detached(value) as { total: number; offset: number; items: ReviewItem[]; observationId?: string; issues?: string[] };
|
|
79
|
+
closed(page, signals ? ["total", "offset", "items", "observationId", "issues"] : ["total", "offset", "items"]);
|
|
80
|
+
if (signals && (page.observationId !== signals.observationId || page.total > 1024 || !Array.isArray(page.issues) || page.issues.length > 1 || page.issues.some(i => i !== "scope-unresolved"))) throw new Error("unbound work signal observation");
|
|
81
|
+
if (!Number.isSafeInteger(page.total) || page.total < 0 || page.total > 4096 || page.offset !== offset || !Array.isArray(page.items) || page.items.length > limit || page.items.length > Math.max(0, page.total - offset)) throw new Error("invalid bounded case page");
|
|
82
|
+
for (const item of page.items) {
|
|
83
|
+
closed(item, ["caseManifestId", "candidate", "priorDecisionId", "disposition"]);
|
|
84
|
+
const c = item.candidate as Record<string, any>;
|
|
85
|
+
if (!sha(item.caseManifestId) || !(signals ? signalCase(c) : caseCheck.Check(c)) || !(item.priorDecisionId === null || sha(item.priorDecisionId)) ||
|
|
86
|
+
!["unresolved", "confirmed_defect", "expected_behavior", "exemplar", "uncertain", "skip"].includes(item.disposition) ||
|
|
87
|
+
(!signals && c.id !== dataDigest({ detector: c.detector, target: c.target, reason: c.reason })) ||
|
|
88
|
+
(!signals && c.classification !== ({ repeat_without_progress: "candidate_defect", economical_exemplar: "candidate_exemplar", coverage_gap: "coverage_issue" } as Record<string, string>)[c.reason]) ||
|
|
89
|
+
(c.reason === "repeat_without_progress" && (c.metrics.equivalentAttempts === undefined || c.metrics.equivalentAttempts < 2)) ||
|
|
90
|
+
(c.reason === "economical_exemplar" && (c.metrics.measuredCost === undefined || c.metrics.costLimit === undefined || c.metrics.costUnit === undefined || c.metrics.measuredCost > c.metrics.costLimit))) throw new Error("invalid case identity/state");
|
|
91
|
+
}
|
|
92
|
+
if (new Set(page.items.map(i => i.caseManifestId)).size !== page.items.length) throw new Error("duplicate case card");
|
|
93
|
+
return freeze(page);
|
|
94
|
+
}
|
|
95
|
+
export function validatedHistory(value: unknown, item: ReviewItem) {
|
|
96
|
+
const history = detached(value) as Record<string, any>[];
|
|
97
|
+
if (!Array.isArray(history) || history.length > 4096) throw new Error("invalid review history");
|
|
98
|
+
let prior: string | null = null;
|
|
99
|
+
for (const d of history) {
|
|
100
|
+
closed(d, ["decision_schema", "id", "caseId", "priorDecisionId", "disposition", "author", "evidence", "note"]);
|
|
101
|
+
const { decision_schema, id, ...body } = d;
|
|
102
|
+
if (decision_schema !== 1 || !sha(id) || dataDigest(body) !== id || d.caseId !== item.candidate.id || d.priorDecisionId !== prior ||
|
|
103
|
+
typeof d.author !== "string" || !d.author || d.author.length > 512 || /[\u0000-\u001f\u007f]/.test(d.author) ||
|
|
104
|
+
!requestCheck.Check({ caseManifestId: item.caseManifestId, priorDecisionId: d.priorDecisionId, disposition: d.disposition, note: d.note }) ||
|
|
105
|
+
!Array.isArray(d.evidence) || d.evidence.length > 256 || !d.evidence.every(sha) || !d.evidence.includes(item.caseManifestId)) throw new Error("unbound review history");
|
|
106
|
+
prior = id;
|
|
107
|
+
}
|
|
108
|
+
return history.at(-1) ?? null;
|
|
109
|
+
}
|
|
110
|
+
export function matchingHistory(value: unknown, item: ReviewItem, request: ReviewRequest, author: string) {
|
|
111
|
+
const last = validatedHistory(value, item);
|
|
112
|
+
const expected = dataDigest({ caseId: item.candidate.id, priorDecisionId: request.priorDecisionId, disposition: request.disposition,
|
|
113
|
+
author, evidence: [item.caseManifestId], note: request.note });
|
|
114
|
+
return last?.id === expected ? expected : null;
|
|
115
|
+
}
|
|
116
|
+
export function blindView(value: unknown): BlindView {
|
|
117
|
+
const view = detached(value) as BlindView; closed(view, ["version", "cards", "limitations"]);
|
|
118
|
+
if (view.version !== "blind-intervention-view-v1" || !Array.isArray(view.cards) || view.cards.length < 2 || view.cards.length > 4 || !Array.isArray(view.limitations) || view.limitations.some(v => typeof v !== "string")) throw new Error("unsupported blind view");
|
|
119
|
+
for (const c of view.cards) { closed(c, ["label", "artifactDigests"]); if (!/^variant-[a-f0-9]{16}$/.test(c.label) || !Array.isArray(c.artifactDigests) || c.artifactDigests.length > 4 || !c.artifactDigests.every(sha)) throw new Error("invalid opaque artifact card"); }
|
|
120
|
+
if (new Set(view.cards.map(c => c.label)).size !== view.cards.length) throw new Error("duplicate blind label"); return freeze(view);
|
|
121
|
+
}
|
|
122
|
+
export function blindChoice(input: BlindChoice, view: BlindView): BlindChoice {
|
|
123
|
+
const c = detached(input); closed(c, ["kind", "labels"]);
|
|
124
|
+
if (!Array.isArray(c.labels) || new Set(c.labels).size !== c.labels.length || c.labels.some(l => !view.cards.some(v => v.label === l)) ||
|
|
125
|
+
!["one", "tie", "none", "insufficient"].includes(c.kind) || (c.kind === "one" ? c.labels.length !== 1 : c.kind === "tie" ? c.labels.length < 2 : c.labels.length !== 0)) throw new Error("invalid quality choice");
|
|
126
|
+
return freeze({ kind: c.kind, labels: [...c.labels].sort() });
|
|
127
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { createDebriefPresenter, type DebriefCheckpoint, type DebriefPersistence } from "./debrief.ts";
|
|
3
|
+
import { blindView, blindChoice, dataKey, dataDigest, detached, freeze, type BlindChoice } from "./debrief-contract.ts";
|
|
4
|
+
import { parseRetentionJson } from "./retention-json.ts";
|
|
5
|
+
const root = new URL("../contracts/debrief/v1/", import.meta.url);
|
|
6
|
+
const json = async (path: string) => parseRetentionJson(await readFile(new URL(path, root), "utf8")) as any;
|
|
7
|
+
/** Explicit frozen demo only. No case writer or durable blind/attention qualification is fabricated. */
|
|
8
|
+
export async function createFixtureDebrief() {
|
|
9
|
+
const cases = await json("work-capture/fixtures/cases.json"), batch = await json("work-capture/fixtures/batch.json");
|
|
10
|
+
if (cases.length !== 7 || batch.candidateIds.length !== 7) throw new Error("fixture inventory changed");
|
|
11
|
+
const items = cases.map((candidate: any, i: number) => ({ candidate, caseManifestId: batch.candidateIds[i], priorDecisionId: null, disposition: "unresolved" })).sort((a: any, b: any) => a.caseManifestId.localeCompare(b.caseManifestId));
|
|
12
|
+
const view = blindView(await json("intervention/fixtures/blind-view.json"));
|
|
13
|
+
let choice: BlindChoice | null = null, revealed = false, state: DebriefCheckpoint | null = null;
|
|
14
|
+
const persistence: DebriefPersistence = { durability: "fixture-memory", load: () => detached(state), compareAndSwap(expected, next) {
|
|
15
|
+
if (expected !== (state ? dataDigest(state) : null)) throw new Error("stale fixture checkpoint"); state = freeze(detached(next));
|
|
16
|
+
} };
|
|
17
|
+
return createDebriefPresenter({ scope: "manual-frozen-fixture-v1", fixture: true, persistence,
|
|
18
|
+
reviewer: { list: (offset, limit) => ({ total: 7, offset, items: items.slice(offset, offset + limit) }), history: () => [] },
|
|
19
|
+
blind: { view: () => view, readArtifact: async (label, digest) => {
|
|
20
|
+
if (!view.cards.some(c => c.label === label && c.artifactDigests.includes(digest))) throw new Error("artifact outside fixture blind view");
|
|
21
|
+
return readFile(new URL(`intervention/fixtures/artifact-${digest}.txt`, root));
|
|
22
|
+
}, choose(input) {
|
|
23
|
+
const next = blindChoice(input, view); if (revealed && dataKey(choice) !== dataKey(next)) throw new Error("fixture choice locked"); choice = next; return next;
|
|
24
|
+
}, async reveal() {
|
|
25
|
+
if (!choice) throw new Error("quality first"); const saved = await json("intervention/fixtures/reveal.json");
|
|
26
|
+
revealed = true; return { ...saved, choice };
|
|
27
|
+
} },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { createDebriefPresenter, type DebriefPersistence } from "./debrief.ts";
|
|
3
|
+
import { closed, detached, freeze, sha, type BlindChoice, type BlindPort, type ReviewPort } from "./debrief-contract.ts";
|
|
4
|
+
/** Host supplies its loaded harness module, not worker metadata or a permission-shaped receipt.
|
|
5
|
+
* Ordinary integration consumes exact638494af... source bytes; this structural port is not module authentication. */
|
|
6
|
+
export interface DebriefHarness {
|
|
7
|
+
createWorkCaseReviewer(root: string, batchId: string, author: string): ReviewPort;
|
|
8
|
+
createWorkSignalReviewer(root: string, batchId: string, author: string): ReviewPort;
|
|
9
|
+
retainBlindIntervention(root: string, manifest: unknown, evidence: unknown[], qualification: unknown, author: string): string;
|
|
10
|
+
openBlindIntervention(root: string, id: string, author: string): BlindPort & { quality(): unknown };
|
|
11
|
+
}
|
|
12
|
+
export interface DurableBlindBinding { comparisonId: string; author: string }
|
|
13
|
+
export type CaseSelection = { version: "work-case-v2"; batchId: string } | { version: "work-signals-v1"; batchId: string; observationId: string };
|
|
14
|
+
function location(root: string, author: string) {
|
|
15
|
+
if (typeof root !== "string" || !isAbsolute(root) || typeof author !== "string" || !author || author.length > 512 || /[\u0000-\u001f\u007f]/.test(author)) throw new Error("explicit archive and independent operator author required");
|
|
16
|
+
}
|
|
17
|
+
function binding(input: DurableBlindBinding): DurableBlindBinding {
|
|
18
|
+
const value = detached(input); closed(value, ["comparisonId", "author"]);
|
|
19
|
+
if (!sha(value.comparisonId) || typeof value.author !== "string") throw new Error("exact retained comparison binding required");
|
|
20
|
+
return freeze(value);
|
|
21
|
+
}
|
|
22
|
+
/** Explicit host retention action only. Caller must retain returned identity independently before exposure. */
|
|
23
|
+
export function retainDebriefBlind(harness: DebriefHarness, root: string, input: { manifest: unknown; evidence: unknown[]; qualification: unknown }, author: string): DurableBlindBinding {
|
|
24
|
+
location(root, author);
|
|
25
|
+
const comparisonId = harness.retainBlindIntervention(root, input.manifest, input.evidence, input.qualification, author);
|
|
26
|
+
return binding({ comparisonId, author });
|
|
27
|
+
}
|
|
28
|
+
/** No choose/reveal/archive/seed/role/cost capability is given to a pre-reveal consumer. */
|
|
29
|
+
export function openDebriefBlindPreview(harness: DebriefHarness, root: string, input: DurableBlindBinding) {
|
|
30
|
+
const b = binding(input); location(root, b.author);
|
|
31
|
+
const port = harness.openBlindIntervention(root, b.comparisonId, b.author);
|
|
32
|
+
return Object.freeze({ view: port.view.bind(port), readArtifact: port.readArtifact.bind(port), quality: port.quality.bind(port) });
|
|
33
|
+
}
|
|
34
|
+
/** Existing presenter + existing durable writers. No second decision history or automatic retention/reveal. */
|
|
35
|
+
export function createRetainedDebrief(harness: DebriefHarness, input: {
|
|
36
|
+
archiveRoot: string; scope: string; author: string; cases: CaseSelection; blind?: DurableBlindBinding;
|
|
37
|
+
persistence: DebriefPersistence; offset?: number;
|
|
38
|
+
}) {
|
|
39
|
+
const root = input.archiveRoot, author = input.author, selection = freeze(detached(input.cases)); location(root, author);
|
|
40
|
+
if (selection.version !== "work-case-v2" && selection.version !== "work-signals-v1") throw new Error("explicit case version required");
|
|
41
|
+
closed(selection, selection.version === "work-signals-v1" ? ["version", "batchId", "observationId"] : ["version", "batchId"]);
|
|
42
|
+
if (!sha(selection.batchId) || selection.version === "work-signals-v1" && !sha(selection.observationId)) throw new Error("exact selected batch/observation required");
|
|
43
|
+
const reviewer = selection.version === "work-signals-v1" ? harness.createWorkSignalReviewer(root, selection.batchId, author) : harness.createWorkCaseReviewer(root, selection.batchId, author);
|
|
44
|
+
const blindBinding = input.blind ? binding(input.blind) : undefined;
|
|
45
|
+
if (blindBinding && blindBinding.author !== author) throw new Error("blind author differs from independent operator");
|
|
46
|
+
const open = harness.openBlindIntervention.bind(harness);
|
|
47
|
+
const blind = blindBinding ? { ...openDebriefBlindPreview(harness, root, blindBinding),
|
|
48
|
+
choose: (choice: BlindChoice) => open(root, blindBinding.comparisonId, author).choose(choice),
|
|
49
|
+
reveal: () => open(root, blindBinding.comparisonId, author).reveal() } : undefined;
|
|
50
|
+
return createDebriefPresenter({ scope: input.scope, offset: input.offset, operatorIdentity: author, reviewer, blind, blindBinding,
|
|
51
|
+
signals: selection.version === "work-signals-v1" ? { batchId: selection.batchId, observationId: selection.observationId } : undefined,
|
|
52
|
+
persistence: input.persistence });
|
|
53
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { DebriefFrame, DebriefPresenter } from "./debrief.ts";
|
|
2
|
+
import { isDebriefPresenter } from "./debrief.ts";
|
|
3
|
+
const safe = (v: unknown) => String(v).replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, " ");
|
|
4
|
+
export function renderDebrief(view: DebriefFrame, width = 100): string {
|
|
5
|
+
const lines = ["PI-DADDY — MANUAL DEBRIEF / WEEKLY QUEUE", `State: ${view.state}; automatic presentation UNQUALIFIED`,
|
|
6
|
+
`Attention: ${view.budgetSpent ?? "UNKNOWN"}/5 allocated slots; ${view.unexposed} incidents not exposed; persistence: ${view.persistence}`,
|
|
7
|
+
view.fixture ? "FROZEN SYNTHETIC FIXTURE — public seed/artifacts, NOT a real blind study" : "Host identity, pause policy and durability are not authenticated by this view."];
|
|
8
|
+
if (view.observation) lines.push(`Selected signal batch: ${view.total} cases; observation ${view.observation.id}`,
|
|
9
|
+
`Observation issues: ${view.observation.issues.join(", ") || "none recorded"}; counts are selected-batch only.`);
|
|
10
|
+
if (view.remainingBudget === null) lines.push("Attention persistence UNKNOWN — no new exposure allowance is inferred.");
|
|
11
|
+
if (view.remainingBudget === 0) lines.push("New questions DEFERRED — budget exhausted. Reopen retains the same slots; answers/skip do not refill them.");
|
|
12
|
+
for (const card of view.cards) {
|
|
13
|
+
if (card.kind === "case") lines.push(`${card.slot}. CASE — ${card.summary}`, ` ${card.disposition} / ${card.resolution}; labels ${card.actionEnabled ? "enabled" : "unavailable or already submitted"}`);
|
|
14
|
+
else {
|
|
15
|
+
lines.push(`${card.slot}. BLIND QUALITY — one shared slot; ${card.choice ? card.choice.kind : "UNSELECTED / UNRESOLVED"}`);
|
|
16
|
+
for (const variant of card.variants) lines.push(` ${variant.label}: ${variant.text.join(" | ")}`);
|
|
17
|
+
lines.push(" Filenames/captions/metadata omitted; arbitrary artifact text may disclose identity.");
|
|
18
|
+
if (card.revealed) {
|
|
19
|
+
lines.push(" AFTER QUALITY CHOICE — no routing/adoption authorized");
|
|
20
|
+
for (const arm of (card.revealed as { arms: { label: string; cost: number | null; costUnit: string; configuration: Record<string, unknown> }[] }).arms) {
|
|
21
|
+
lines.push(` ${arm.label}: ${arm.cost ?? "unknown"} ${arm.costUnit}`);
|
|
22
|
+
for (const [key, value] of Object.entries(arm.configuration)) lines.push(` ${key}: ${String(value)}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else lines.push(` Configuration/cost hidden; reveal ${card.choiceConfirmed ? "available" : "locked"}. Choices: one / tie / none / insufficient.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
lines.push("Skip and unselected remain unresolved. Labels are not work acceptance, causal proof or routing/adoption.",
|
|
29
|
+
"Own dashboard commands: label SLOT DISPOSITION NOTE | choose KIND LABEL... | reveal | reconcile SLOT | close | open",
|
|
30
|
+
"No worker messages, control dispatch or automatic recovery. Missing host callbacks disable actions.");
|
|
31
|
+
const w = Math.max(40, Math.min(240, Math.floor(width) || 100));
|
|
32
|
+
return lines.slice(0, 80).map(line => { const text = safe(line); return text.length > w ? text.slice(0, w - 1) + "…" : text; }).join("\n");
|
|
33
|
+
}
|
|
34
|
+
/** The actual existing dashboard action seam; no terminal/worker transport or authority metadata. */
|
|
35
|
+
export async function debriefAction(presenter: DebriefPresenter, command: string): Promise<unknown> {
|
|
36
|
+
if (!isDebriefPresenter(presenter) || typeof command !== "string" || command.length > 4096) throw new Error("invalid debrief action");
|
|
37
|
+
const [action, ...args] = command.trim().split(/\s+/), view = presenter.view();
|
|
38
|
+
if (action === "close" && !args.length) { return presenter.close(); }
|
|
39
|
+
if (action === "open" && !args.length) { return presenter.open({ mode: "manual", userPresent: true }); }
|
|
40
|
+
if (action === "reveal" && !args.length) { return presenter.reveal(); }
|
|
41
|
+
if (action === "choose") { return presenter.choose({ kind: args[0] as "one", labels: args.slice(1) }); }
|
|
42
|
+
const slot = Number(args[0]), card = view.cards.find(c => c.slot === slot);
|
|
43
|
+
if (card?.kind !== "case") throw new Error("exact exposed case slot required");
|
|
44
|
+
if (action === "reconcile" && args.length === 1) { return presenter.reconcile(card.caseManifestId); }
|
|
45
|
+
if (action === "label" && args.length >= 2) {
|
|
46
|
+
return presenter.label({ caseManifestId: card.caseManifestId, priorDecisionId: card.priorDecisionId, disposition: args[1] as "skip", note: args.slice(2).join(" ") });
|
|
47
|
+
}
|
|
48
|
+
throw new Error("unsupported debrief action");
|
|
49
|
+
}
|