@llblab/pi-kit 0.38.3 → 0.39.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 +10 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-actors/AGENTS.md +108 -20
- package/node_modules/@llblab/pi-actors/BACKLOG.md +207 -4
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +13 -0
- package/node_modules/@llblab/pi-actors/README.md +31 -1
- package/node_modules/@llblab/pi-actors/dist/index.js +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +37 -0
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +131 -62
- package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.d.ts +2 -0
- package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.js +3 -2
- package/node_modules/@llblab/pi-actors/dist/lib/command-templates.d.ts +37 -0
- package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +263 -129
- package/node_modules/@llblab/pi-actors/dist/lib/execution-files.d.ts +32 -0
- package/node_modules/@llblab/pi-actors/dist/lib/execution-files.js +128 -0
- package/node_modules/@llblab/pi-actors/dist/lib/execution-output.js +5 -4
- package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.d.ts +19 -1
- package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.js +62 -26
- package/node_modules/@llblab/pi-actors/dist/lib/execution.d.ts +4 -1
- package/node_modules/@llblab/pi-actors/dist/lib/execution.js +6 -1
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.d.ts +9 -2
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +92 -14
- package/node_modules/@llblab/pi-actors/dist/lib/file-state.js +21 -2
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-command.js +2 -2
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.d.ts +25 -7
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.js +562 -262
- package/node_modules/@llblab/pi-actors/dist/lib/inspector.d.ts +20 -10
- package/node_modules/@llblab/pi-actors/dist/lib/inspector.js +80 -91
- package/node_modules/@llblab/pi-actors/dist/lib/limits.d.ts +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/limits.js +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +15 -1
- package/node_modules/@llblab/pi-actors/dist/lib/observability.js +216 -99
- package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +5 -4
- package/node_modules/@llblab/pi-actors/dist/lib/pi.js +64 -10
- package/node_modules/@llblab/pi-actors/dist/lib/recipe-projection.d.ts +18 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipe-projection.js +220 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.d.ts +2 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.js +30 -6
- package/node_modules/@llblab/pi-actors/dist/lib/registry.js +270 -36
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +213 -5
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +1909 -40
- package/node_modules/@llblab/pi-actors/dist/lib/run-supervision.d.ts +107 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-supervision.js +412 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.d.ts +9 -2
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +283 -60
- package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.d.ts +26 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.js +68 -2
- package/node_modules/@llblab/pi-actors/dist/lib/runs-context-exports.d.ts +451 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-context-exports.js +2315 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-context-index.d.ts +97 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-context-index.js +368 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-control-delivery.js +46 -7
- package/node_modules/@llblab/pi-actors/dist/lib/runs-control.d.ts +78 -2
- package/node_modules/@llblab/pi-actors/dist/lib/runs-control.js +334 -2
- package/node_modules/@llblab/pi-actors/dist/lib/runs-controls.js +4 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-descendant-launches.d.ts +80 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-descendant-launches.js +287 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-execution-evidence.d.ts +110 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-execution-evidence.js +282 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-index.d.ts +66 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-index.js +349 -13
- package/node_modules/@llblab/pi-actors/dist/lib/runs-native-launches.d.ts +73 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-native-launches.js +227 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-outcomes.d.ts +141 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-outcomes.js +292 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.d.ts +10 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.js +36 -4
- package/node_modules/@llblab/pi-actors/dist/lib/runs-process-windows-mi.d.ts +46 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-process-windows-mi.js +114 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-process.d.ts +34 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-process.js +158 -26
- package/node_modules/@llblab/pi-actors/dist/lib/runs-results.d.ts +116 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-results.js +518 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.d.ts +15 -2
- package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.js +148 -9
- package/node_modules/@llblab/pi-actors/dist/lib/runs-session-exports.d.ts +95 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-session-exports.js +470 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-snapshot.d.ts +58 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-snapshot.js +92 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-start.d.ts +2 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-start.js +44 -3
- package/node_modules/@llblab/pi-actors/dist/lib/runs-status.d.ts +15 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-status.js +337 -13
- package/node_modules/@llblab/pi-actors/dist/lib/runs-supervision.d.ts +105 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-supervision.js +501 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.d.ts +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.js +14 -3
- package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +22 -3
- package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.d.ts +37 -0
- package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.js +59 -3
- package/node_modules/@llblab/pi-actors/dist/lib/state-readers.d.ts +4 -1
- package/node_modules/@llblab/pi-actors/dist/lib/state-readers.js +14 -8
- package/node_modules/@llblab/pi-actors/dist/lib/temp.d.ts +2 -4
- package/node_modules/@llblab/pi-actors/dist/lib/temp.js +4 -56
- package/node_modules/@llblab/pi-actors/dist/lib/tools-access.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-access.js +13 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.d.ts +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.js +110 -43
- package/node_modules/@llblab/pi-actors/dist/lib/tools-local.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-local.js +18 -6
- package/node_modules/@llblab/pi-actors/dist/lib/tools-message.d.ts +4 -1
- package/node_modules/@llblab/pi-actors/dist/lib/tools-message.js +126 -31
- package/node_modules/@llblab/pi-actors/dist/lib/tools-response.d.ts +31 -3
- package/node_modules/@llblab/pi-actors/dist/lib/tools-response.js +311 -47
- package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.js +29 -7
- package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.d.ts +20 -0
- package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.js +192 -43
- package/node_modules/@llblab/pi-actors/dist/scripts/async-runner.mjs +272 -194
- package/node_modules/@llblab/pi-actors/dist/scripts/conformance.mjs +14 -2
- package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +46 -2
- package/node_modules/@llblab/pi-actors/dist/skills/actors/references/diagnostics.md +40 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/references/runs.md +55 -1
- package/node_modules/@llblab/pi-actors/docs/README.md +9 -1
- package/node_modules/@llblab/pi-actors/docs/actor-inspector.md +84 -15
- package/node_modules/@llblab/pi-actors/docs/agent-experience.md +216 -0
- package/node_modules/@llblab/pi-actors/docs/async-runs.md +18 -2
- package/node_modules/@llblab/pi-actors/docs/composition-transport.md +103 -0
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +204 -4
- package/node_modules/@llblab/pi-actors/docs/inspection.md +38 -2
- package/node_modules/@llblab/pi-actors/docs/output-projection-map.md +130 -0
- package/node_modules/@llblab/pi-actors/docs/releasing.md +19 -2
- package/node_modules/@llblab/pi-actors/docs/run-supervision.md +1622 -0
- package/node_modules/@llblab/pi-actors/docs/template-recipes.md +4 -0
- package/node_modules/@llblab/pi-actors/docs/windows-process-observation.md +248 -0
- package/node_modules/@llblab/pi-actors/index.ts +1 -1
- package/node_modules/@llblab/pi-actors/lib/async-runs.ts +164 -73
- package/node_modules/@llblab/pi-actors/lib/automatic-review-runtime.ts +4 -2
- package/node_modules/@llblab/pi-actors/lib/command-templates.ts +287 -138
- package/node_modules/@llblab/pi-actors/lib/execution-files.ts +118 -0
- package/node_modules/@llblab/pi-actors/lib/execution-output.ts +4 -4
- package/node_modules/@llblab/pi-actors/lib/execution-sessions.ts +57 -27
- package/node_modules/@llblab/pi-actors/lib/execution.ts +8 -0
- package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +102 -12
- package/node_modules/@llblab/pi-actors/lib/file-state.ts +16 -2
- package/node_modules/@llblab/pi-actors/lib/inspector-command.ts +2 -2
- package/node_modules/@llblab/pi-actors/lib/inspector-overlay.ts +524 -261
- package/node_modules/@llblab/pi-actors/lib/inspector.ts +101 -130
- package/node_modules/@llblab/pi-actors/lib/limits.ts +1 -0
- package/node_modules/@llblab/pi-actors/lib/observability.ts +235 -116
- package/node_modules/@llblab/pi-actors/lib/pi.ts +59 -11
- package/node_modules/@llblab/pi-actors/lib/recipe-projection.ts +232 -0
- package/node_modules/@llblab/pi-actors/lib/recipes-context.ts +27 -6
- package/node_modules/@llblab/pi-actors/lib/registry.ts +266 -36
- package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +1737 -49
- package/node_modules/@llblab/pi-actors/lib/run-supervision.ts +411 -0
- package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +237 -73
- package/node_modules/@llblab/pi-actors/lib/runs-artifacts.ts +73 -1
- package/node_modules/@llblab/pi-actors/lib/runs-context-exports.ts +1871 -0
- package/node_modules/@llblab/pi-actors/lib/runs-context-index.ts +372 -0
- package/node_modules/@llblab/pi-actors/lib/runs-control-delivery.ts +36 -18
- package/node_modules/@llblab/pi-actors/lib/runs-control.ts +297 -1
- package/node_modules/@llblab/pi-actors/lib/runs-controls.ts +3 -1
- package/node_modules/@llblab/pi-actors/lib/runs-descendant-launches.ts +338 -0
- package/node_modules/@llblab/pi-actors/lib/runs-execution-evidence.ts +302 -0
- package/node_modules/@llblab/pi-actors/lib/runs-index.ts +345 -17
- package/node_modules/@llblab/pi-actors/lib/runs-native-launches.ts +270 -0
- package/node_modules/@llblab/pi-actors/lib/runs-outcomes.ts +377 -0
- package/node_modules/@llblab/pi-actors/lib/runs-ownership.ts +37 -4
- package/node_modules/@llblab/pi-actors/lib/runs-process-windows-mi.ts +113 -0
- package/node_modules/@llblab/pi-actors/lib/runs-process.ts +210 -34
- package/node_modules/@llblab/pi-actors/lib/runs-results.ts +601 -0
- package/node_modules/@llblab/pi-actors/lib/runs-retention.ts +134 -10
- package/node_modules/@llblab/pi-actors/lib/runs-session-exports.ts +406 -0
- package/node_modules/@llblab/pi-actors/lib/runs-snapshot.ts +143 -0
- package/node_modules/@llblab/pi-actors/lib/runs-start.ts +44 -2
- package/node_modules/@llblab/pi-actors/lib/runs-status.ts +252 -10
- package/node_modules/@llblab/pi-actors/lib/runs-supervision.ts +394 -0
- package/node_modules/@llblab/pi-actors/lib/runs-trace.ts +14 -3
- package/node_modules/@llblab/pi-actors/lib/runtime.ts +22 -3
- package/node_modules/@llblab/pi-actors/lib/session-evidence.ts +76 -4
- package/node_modules/@llblab/pi-actors/lib/state-readers.ts +16 -7
- package/node_modules/@llblab/pi-actors/lib/temp.ts +3 -56
- package/node_modules/@llblab/pi-actors/lib/tools-access.ts +13 -0
- package/node_modules/@llblab/pi-actors/lib/tools-inspect.ts +110 -51
- package/node_modules/@llblab/pi-actors/lib/tools-local.ts +37 -24
- package/node_modules/@llblab/pi-actors/lib/tools-message.ts +120 -37
- package/node_modules/@llblab/pi-actors/lib/tools-response.ts +273 -50
- package/node_modules/@llblab/pi-actors/lib/tools-spawn.ts +31 -10
- package/node_modules/@llblab/pi-actors/lib/trace-projection.ts +165 -42
- package/node_modules/@llblab/pi-actors/package.json +10 -6
- package/node_modules/@llblab/pi-actors/scripts/async-runner.mjs +272 -194
- package/node_modules/@llblab/pi-actors/scripts/conformance.mjs +14 -2
- package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +46 -2
- package/node_modules/@llblab/pi-actors/skills/actors/references/diagnostics.md +40 -0
- package/node_modules/@llblab/pi-actors/skills/actors/references/runs.md +55 -1
- package/node_modules/@llblab/pi-codex-usage/AGENTS.md +2 -2
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +9 -5
- package/node_modules/@llblab/pi-codex-usage/lib/query.ts +229 -50
- package/node_modules/@llblab/pi-codex-usage/lib/usage.ts +9 -1
- package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
- package/node_modules/yaml/LICENSE +13 -0
- package/node_modules/yaml/README.md +172 -0
- package/node_modules/yaml/bin.mjs +11 -0
- package/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
- package/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
- package/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
- package/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
- package/node_modules/yaml/browser/dist/compose/composer.js +219 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
- package/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
- package/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
- package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +225 -0
- package/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
- package/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
- package/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
- package/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
- package/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
- package/node_modules/yaml/browser/dist/doc/Document.js +335 -0
- package/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
- package/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
- package/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
- package/node_modules/yaml/browser/dist/doc/directives.js +176 -0
- package/node_modules/yaml/browser/dist/errors.js +57 -0
- package/node_modules/yaml/browser/dist/index.js +17 -0
- package/node_modules/yaml/browser/dist/log.js +11 -0
- package/node_modules/yaml/browser/dist/nodes/Alias.js +116 -0
- package/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
- package/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
- package/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
- package/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
- package/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
- package/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
- package/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
- package/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
- package/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
- package/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
- package/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
- package/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
- package/node_modules/yaml/browser/dist/parse/cst.js +98 -0
- package/node_modules/yaml/browser/dist/parse/lexer.js +721 -0
- package/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
- package/node_modules/yaml/browser/dist/parse/parser.js +975 -0
- package/node_modules/yaml/browser/dist/public-api.js +102 -0
- package/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
- package/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
- package/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
- package/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
- package/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
- package/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
- package/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
- package/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
- package/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
- package/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
- package/node_modules/yaml/browser/dist/schema/tags.js +96 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +67 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
- package/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
- package/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +25 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
- package/node_modules/yaml/browser/dist/util.js +11 -0
- package/node_modules/yaml/browser/dist/visit.js +233 -0
- package/node_modules/yaml/browser/index.js +5 -0
- package/node_modules/yaml/browser/package.json +3 -0
- package/node_modules/yaml/dist/cli.d.ts +8 -0
- package/node_modules/yaml/dist/cli.mjs +201 -0
- package/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
- package/node_modules/yaml/dist/compose/compose-collection.js +90 -0
- package/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
- package/node_modules/yaml/dist/compose/compose-doc.js +45 -0
- package/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
- package/node_modules/yaml/dist/compose/compose-node.js +112 -0
- package/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
- package/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
- package/node_modules/yaml/dist/compose/composer.d.ts +63 -0
- package/node_modules/yaml/dist/compose/composer.js +224 -0
- package/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
- package/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
- package/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
- package/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
- package/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-end.js +39 -0
- package/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
- package/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
- package/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
- package/node_modules/yaml/dist/compose/resolve-props.js +148 -0
- package/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
- package/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
- package/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
- package/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
- package/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
- package/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
- package/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
- package/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
- package/node_modules/yaml/dist/doc/Document.d.ts +141 -0
- package/node_modules/yaml/dist/doc/Document.js +337 -0
- package/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
- package/node_modules/yaml/dist/doc/anchors.js +76 -0
- package/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
- package/node_modules/yaml/dist/doc/applyReviver.js +57 -0
- package/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
- package/node_modules/yaml/dist/doc/createNode.js +90 -0
- package/node_modules/yaml/dist/doc/directives.d.ts +49 -0
- package/node_modules/yaml/dist/doc/directives.js +178 -0
- package/node_modules/yaml/dist/errors.d.ts +21 -0
- package/node_modules/yaml/dist/errors.js +62 -0
- package/node_modules/yaml/dist/index.d.ts +25 -0
- package/node_modules/yaml/dist/index.js +50 -0
- package/node_modules/yaml/dist/log.d.ts +3 -0
- package/node_modules/yaml/dist/log.js +19 -0
- package/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
- package/node_modules/yaml/dist/nodes/Alias.js +118 -0
- package/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
- package/node_modules/yaml/dist/nodes/Collection.js +151 -0
- package/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
- package/node_modules/yaml/dist/nodes/Node.js +40 -0
- package/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
- package/node_modules/yaml/dist/nodes/Pair.js +39 -0
- package/node_modules/yaml/dist/nodes/Scalar.d.ts +47 -0
- package/node_modules/yaml/dist/nodes/Scalar.js +27 -0
- package/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
- package/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
- package/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
- package/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
- package/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
- package/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
- package/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
- package/node_modules/yaml/dist/nodes/identity.js +53 -0
- package/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
- package/node_modules/yaml/dist/nodes/toJS.js +39 -0
- package/node_modules/yaml/dist/options.d.ts +350 -0
- package/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
- package/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
- package/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
- package/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
- package/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
- package/node_modules/yaml/dist/parse/cst-visit.js +99 -0
- package/node_modules/yaml/dist/parse/cst.d.ts +109 -0
- package/node_modules/yaml/dist/parse/cst.js +112 -0
- package/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
- package/node_modules/yaml/dist/parse/lexer.js +723 -0
- package/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
- package/node_modules/yaml/dist/parse/line-counter.js +41 -0
- package/node_modules/yaml/dist/parse/parser.d.ts +84 -0
- package/node_modules/yaml/dist/parse/parser.js +980 -0
- package/node_modules/yaml/dist/public-api.d.ts +44 -0
- package/node_modules/yaml/dist/public-api.js +107 -0
- package/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
- package/node_modules/yaml/dist/schema/Schema.js +39 -0
- package/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/map.js +19 -0
- package/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
- package/node_modules/yaml/dist/schema/common/null.js +17 -0
- package/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/seq.js +19 -0
- package/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/string.js +16 -0
- package/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/bool.js +21 -0
- package/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/float.js +47 -0
- package/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/int.js +42 -0
- package/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
- package/node_modules/yaml/dist/schema/core/schema.js +25 -0
- package/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
- package/node_modules/yaml/dist/schema/json/schema.js +64 -0
- package/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
- package/node_modules/yaml/dist/schema/tags.d.ts +48 -0
- package/node_modules/yaml/dist/schema/tags.js +99 -0
- package/node_modules/yaml/dist/schema/types.d.ts +92 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
- package/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
- package/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
- package/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
- package/node_modules/yaml/dist/stringify/stringify.js +132 -0
- package/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
- package/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
- package/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
- package/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
- package/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
- package/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
- package/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
- package/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
- package/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
- package/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
- package/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
- package/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
- package/node_modules/yaml/dist/test-events.d.ts +4 -0
- package/node_modules/yaml/dist/test-events.js +134 -0
- package/node_modules/yaml/dist/util.d.ts +16 -0
- package/node_modules/yaml/dist/util.js +28 -0
- package/node_modules/yaml/dist/visit.d.ts +102 -0
- package/node_modules/yaml/dist/visit.js +236 -0
- package/node_modules/yaml/package.json +97 -0
- package/node_modules/yaml/util.js +2 -0
- package/package.json +3 -3
- package/node_modules/@llblab/pi-actors/banner.jpg +0 -0
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.d.ts +0 -16
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.js +0 -57
- package/node_modules/@llblab/pi-actors/lib/inspector-actions.ts +0 -79
|
@@ -0,0 +1,2315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Producer-bound Run context, primary text and index orchestration.
|
|
3
|
+
* Owns: admitted privacy capture, atomic derived views, producer-backed saved Recipe/Trace/Control/Execution/Result correspondence, immutable primary source publication/readonly admission and index assembly.
|
|
4
|
+
* Excludes: public routing, session/composed/failure primary publication, live clocks and native work/control authority.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
+
import { closeSync, constants, fstatSync, linkSync, lstatSync, mkdirSync, openSync, readSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { readExecutionCaptureText, RUN_STREAM_SOURCE_MAX_BYTES } from "./execution-files.js";
|
|
9
|
+
import { isAbsolute, join, sep } from "node:path";
|
|
10
|
+
import { isPromise } from "node:util/types";
|
|
11
|
+
import { isDeepStrictEqual } from "node:util";
|
|
12
|
+
import { normalizeControlAction, normalizeControlInput } from "./control.js";
|
|
13
|
+
import { projectRunControl } from "./control-projection.js";
|
|
14
|
+
import { projectRunExecutionManifest } from "./runs-execution-evidence.js";
|
|
15
|
+
import { writeTextAtomic } from "./file-state.js";
|
|
16
|
+
import { COMPACT_PREVIEW_CHARS, DEFAULT_INSPECT_LINES, RUN_COMPLETION_MODEL_MAX_BYTES, RUN_DELIVERY_JOURNAL_MAX_BYTES, RUN_DELIVERY_MODEL_MAX_BYTES, RUN_CONTROL_JOURNAL_MAX_BYTES, TRACE_JOURNAL_MAX_BYTES } from "./limits.js";
|
|
17
|
+
import { createRunRecipeBodyRedactor, createRunRecipeTextRedactor, projectRunRecipe, capturedSecretMask, createContextIdentityPrivacyCheck } from "./recipe-projection.js";
|
|
18
|
+
import { computeRunControlCapacity } from "./run-evidence-policy.js";
|
|
19
|
+
import { readRunSupervisionClock, createRunSupervisionTimebase } from "./run-supervision.js";
|
|
20
|
+
import { classifyRuntimeControl } from "./runtime-triage.js";
|
|
21
|
+
import { runContextExportPath, writeRunContextIndexUnderLock, readRunContextIdentity, readRunContextIndex, RUN_CONTEXT_INDEX_FILE, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES } from "./runs-context-index.js";
|
|
22
|
+
import { readRunControlJournalFromStateDir, runControlsFile } from "./runs-controls.js";
|
|
23
|
+
import { assertOwnedRunStateDirectory, RUN_STATE_OWNERSHIP_FILE } from "./runs-ownership.js";
|
|
24
|
+
import { describeRunArtifactReferences, observeRunArtifactReference } from "./runs-artifacts.js";
|
|
25
|
+
import { decodeRunPrimaryTextBinding, readRunNaturalOutcomeSnapshot, RUN_OUTCOME_PROTOCOL, RUN_PRIMARY_TEXT_PROTOCOL, RUN_ARTIFACT_INVENTORY_PROTOCOL, decodeRunArtifactInventoryBinding, RUN_RESULT_DIAGNOSTIC_FIELDS as RESULT_DIAGNOSTIC_FIELDS, RUN_RESULT_DIAGNOSTICS_PROTOCOL, decodeRunResultDiagnosticsBinding, RUN_RESULT_DIAGNOSTIC_EXCERPT_CHARS, decodeRunResultDiagnosticExcerpt } from "./runs-outcomes.js";
|
|
26
|
+
import { withRunProducerEvidence, readRunProducerCapturedMetadata, readRunProducerCleanupFailure, withRunResultDiagnosticsAdmission } from "./runs-results.js";
|
|
27
|
+
import { readRunTraceJournal, runTraceFile, summarizeRunTraceJournal } from "./runs-trace.js";
|
|
28
|
+
import { readJsonFileResilient, readJsonlFileResilient } from "./state-readers.js";
|
|
29
|
+
import { projectRunTraceJournal } from "./trace-projection.js";
|
|
30
|
+
import { redactSessionEvidenceValue } from "./session-evidence.js";
|
|
31
|
+
export { readRunSessionContextPreview, writeRunSessionContextExport, writeRunSessionContextIndex, RUN_CONTEXT_SESSIONS_MAX_BYTES } from "./runs-session-exports.js";
|
|
32
|
+
export const RUN_CONTEXT_CONTROL_MAX_BYTES = 64 * 1024;
|
|
33
|
+
const CONTROL_VIEW_SCHEMA = "run-context-control-v1";
|
|
34
|
+
const CONTROL_VIEW_COVERAGE = Object.freeze({ truncated: true, history_complete: false, redacted: true });
|
|
35
|
+
export const RUN_CONTEXT_RECIPE_MAX_BYTES = 64 * 1024;
|
|
36
|
+
const RECIPE_VIEW_COVERAGE = Object.freeze({ truncated: true, redacted: true });
|
|
37
|
+
export const RUN_CONTEXT_TRACE_MAX_BYTES = 64 * 1024;
|
|
38
|
+
const TRACE_VIEW_COVERAGE = Object.freeze({ truncated: true, history_complete: false, redacted: true });
|
|
39
|
+
export const RUN_CONTEXT_RESULT_MAX_BYTES = 64 * 1024;
|
|
40
|
+
export const RUN_RESULT_DIAGNOSTICS_MAX_BYTES = 4 * 1024 * 1024;
|
|
41
|
+
const RESULT_DIAGNOSTIC_PREVIEW_CHARS = 4_000;
|
|
42
|
+
const RESULT_VIEW_COVERAGE = Object.freeze({ truncated: true, redacted: true });
|
|
43
|
+
export const RUN_CONTEXT_ARTIFACT_MAX_BYTES = 64 * 1024;
|
|
44
|
+
export const RUN_ARTIFACT_INVENTORY_MAX_BYTES = 4 * 1024 * 1024;
|
|
45
|
+
const ARTIFACT_VIEW_COVERAGE = Object.freeze({ truncated: false, redacted: true });
|
|
46
|
+
export const RUN_CONTEXT_EXECUTION_MAX_BYTES = 64 * 1024;
|
|
47
|
+
const EXECUTION_VIEW_COVERAGE = Object.freeze({ truncated: true, history_complete: false, redacted: true });
|
|
48
|
+
const VIEW_CONFIG = {
|
|
49
|
+
control: { schema: CONTROL_VIEW_SCHEMA, format: "json", media: "application/json", maxBytes: RUN_CONTEXT_CONTROL_MAX_BYTES, coverage: CONTROL_VIEW_COVERAGE },
|
|
50
|
+
recipe: { schema: "run-context-recipe-v1", format: "json", media: "application/json", maxBytes: RUN_CONTEXT_RECIPE_MAX_BYTES, coverage: RECIPE_VIEW_COVERAGE },
|
|
51
|
+
trace: { schema: "run-context-trace-v1", format: "jsonl", media: "application/x-ndjson", maxBytes: RUN_CONTEXT_TRACE_MAX_BYTES, coverage: TRACE_VIEW_COVERAGE },
|
|
52
|
+
result: { schema: "run-context-result-v1", format: "json", media: "application/json", maxBytes: RUN_CONTEXT_RESULT_MAX_BYTES, coverage: RESULT_VIEW_COVERAGE },
|
|
53
|
+
artifacts: { schema: "run-context-artifacts-v1", format: "json", media: "application/json", maxBytes: RUN_CONTEXT_ARTIFACT_MAX_BYTES, coverage: ARTIFACT_VIEW_COVERAGE },
|
|
54
|
+
execution: { schema: "run-context-execution-v1", format: "json", media: "application/json", maxBytes: RUN_CONTEXT_EXECUTION_MAX_BYTES, coverage: EXECUTION_VIEW_COVERAGE },
|
|
55
|
+
};
|
|
56
|
+
class ContextExportBindingRefusal extends Error {
|
|
57
|
+
}
|
|
58
|
+
// Producer-scoped admitted Trace bytes, never a generic registry or saved/hash-reconstructed capability.
|
|
59
|
+
const tracePublications = new WeakMap();
|
|
60
|
+
// Separate role authority: a successful Trace publication never admits a Control view.
|
|
61
|
+
const controlPublications = new WeakMap();
|
|
62
|
+
const executionPublications = new WeakMap();
|
|
63
|
+
const resultPublications = new WeakMap();
|
|
64
|
+
function executionUnavailable(reason) { return Object.freeze({ status: "unavailable", reason }); }
|
|
65
|
+
function artifactUnavailable(reason) { return Object.freeze({ status: "unavailable", reason }); }
|
|
66
|
+
function resultUnavailable(reason) { return Object.freeze({ status: "unavailable", reason }); }
|
|
67
|
+
function traceUnavailable(reason) { return Object.freeze({ status: "unavailable", reason }); }
|
|
68
|
+
function recipeUnavailable(reason) { return Object.freeze({ status: "unavailable", reason }); }
|
|
69
|
+
function unavailable(reason) { return Object.freeze({ status: "unavailable", reason }); }
|
|
70
|
+
function sameObject(left, right) { return left.dev === right.dev && left.ino === right.ino; }
|
|
71
|
+
function sameFile(left, right) {
|
|
72
|
+
return right.isFile() && sameObject(left, right) && left.size === right.size &&
|
|
73
|
+
left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
|
|
74
|
+
}
|
|
75
|
+
function optionalStamp(path) {
|
|
76
|
+
try {
|
|
77
|
+
return lstatSync(path, { bigint: true });
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error.code === "ENOENT")
|
|
81
|
+
return undefined;
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function captureControl(stateDir, producer, status) {
|
|
86
|
+
const metadata = readContextMetadata(stateDir, producer);
|
|
87
|
+
if (metadata.state === "unavailable")
|
|
88
|
+
return unavailable(metadata.reason === "changed" ? "control_metadata_changed" : "control_metadata_unavailable");
|
|
89
|
+
const unchanged = captureContextMetadataFence(metadata, producer);
|
|
90
|
+
if (!unchanged)
|
|
91
|
+
return unavailable("control_metadata_changed");
|
|
92
|
+
const original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
93
|
+
if (!original)
|
|
94
|
+
return unavailable("control_admission_unavailable");
|
|
95
|
+
const redact = createRunRecipeBodyRedactor(original, Number.MAX_SAFE_INTEGER);
|
|
96
|
+
const identityUnchanged = createContextIdentityPrivacyCheck(original);
|
|
97
|
+
const headerIdentity = { run: producer.run, run_instance_id: producer.runInstanceId };
|
|
98
|
+
if (!identityUnchanged(headerIdentity))
|
|
99
|
+
return unavailable("control_identity_redacted");
|
|
100
|
+
const path = runControlsFile(stateDir);
|
|
101
|
+
const before = optionalStamp(path);
|
|
102
|
+
if (!before)
|
|
103
|
+
return unavailable("control_journal_absent");
|
|
104
|
+
if (!before.isFile() || before.size > BigInt(RUN_CONTROL_JOURNAL_MAX_BYTES))
|
|
105
|
+
return unavailable("control_journal_unavailable");
|
|
106
|
+
const journal = readRunControlJournalFromStateDir(stateDir);
|
|
107
|
+
if (journal.diagnostics.length)
|
|
108
|
+
return unavailable("control_journal_invalid");
|
|
109
|
+
const after = optionalStamp(path);
|
|
110
|
+
if (!after || !sameFile(before, after))
|
|
111
|
+
return unavailable("control_journal_changed");
|
|
112
|
+
const current = [];
|
|
113
|
+
let foreign = 0;
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
const timestampFields = ["queued_at", "delivered_at", "claimed_at", "handled_at", "failed_at"];
|
|
116
|
+
for (const value of journal.records) {
|
|
117
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
118
|
+
return unavailable("control_journal_invalid");
|
|
119
|
+
const record = value;
|
|
120
|
+
if (typeof record.run_instance_id !== "string" || !record.run_instance_id)
|
|
121
|
+
return unavailable("control_journal_invalid");
|
|
122
|
+
if (record.run_instance_id !== producer.runInstanceId) {
|
|
123
|
+
foreign++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
normalizeControlAction(record.action);
|
|
128
|
+
if (record.input !== undefined)
|
|
129
|
+
normalizeControlInput(record.input);
|
|
130
|
+
if (record.error !== undefined && typeof record.error !== "string")
|
|
131
|
+
return unavailable("control_journal_invalid");
|
|
132
|
+
for (const field of timestampFields) {
|
|
133
|
+
const timestamp = record[field];
|
|
134
|
+
if ((field === "queued_at" || timestamp !== undefined) &&
|
|
135
|
+
(typeof timestamp !== "string" || !Number.isFinite(Date.parse(timestamp))))
|
|
136
|
+
return unavailable("control_journal_invalid");
|
|
137
|
+
}
|
|
138
|
+
if (classifyRuntimeControl({ run: producer.run, runInstanceId: producer.runInstanceId, status }, value, now).diagnostic) {
|
|
139
|
+
return unavailable("control_journal_invalid");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return unavailable("control_journal_invalid");
|
|
144
|
+
}
|
|
145
|
+
current.push(record);
|
|
146
|
+
}
|
|
147
|
+
const capacity = computeRunControlCapacity(current);
|
|
148
|
+
if (capacity.oldest_pending_at !== undefined && !identityUnchanged({ oldest_pending_at: capacity.oldest_pending_at })) {
|
|
149
|
+
return unavailable("control_metadata_redacted");
|
|
150
|
+
}
|
|
151
|
+
const capturedAt = new Date().toISOString();
|
|
152
|
+
if (!identityUnchanged({ captured_at: capturedAt }))
|
|
153
|
+
return unavailable("control_metadata_redacted");
|
|
154
|
+
// Safe references stay exact, never Inspector previews; privacy-changed references omit the whole row.
|
|
155
|
+
const controls = current.slice(-DEFAULT_INSPECT_LINES).flatMap(control => {
|
|
156
|
+
const identity = { id: control.id, run_instance_id: control.run_instance_id, action: control.action,
|
|
157
|
+
...Object.fromEntries(timestampFields.flatMap(field => control[field] === undefined ? [] : [[field, control[field]]])) };
|
|
158
|
+
if (!identityUnchanged(identity))
|
|
159
|
+
return [];
|
|
160
|
+
return [{ ...projectRunControl({ ...control,
|
|
161
|
+
...(control.input === undefined ? {} : { input: redact(control.input) }),
|
|
162
|
+
...(control.error === undefined ? {} : { error: redact(control.error) }) }), ...identity }];
|
|
163
|
+
});
|
|
164
|
+
const encode = () => JSON.stringify({ schema: CONTROL_VIEW_SCHEMA, run: producer.run, run_instance_id: producer.runInstanceId,
|
|
165
|
+
captured_at: capturedAt, run_status: status, coverage: CONTROL_VIEW_COVERAGE,
|
|
166
|
+
capacity, controls,
|
|
167
|
+
controls_omitted: current.length - controls.length, foreign_controls_omitted: foreign }, null, 2) + "\n";
|
|
168
|
+
let content = encode();
|
|
169
|
+
while (controls.length && Buffer.byteLength(content, "utf8") > RUN_CONTEXT_CONTROL_MAX_BYTES) {
|
|
170
|
+
controls.shift();
|
|
171
|
+
content = encode();
|
|
172
|
+
}
|
|
173
|
+
if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_CONTROL_MAX_BYTES)
|
|
174
|
+
return unavailable("control_journal_unavailable");
|
|
175
|
+
// Fence the admitted metadata frame, not journal freshness after this recorded snapshot.
|
|
176
|
+
return unchanged() ? Object.freeze({ status: "captured", captured_at: capturedAt, content, unchanged, original_root: metadata.root }) : unavailable("control_metadata_changed");
|
|
177
|
+
}
|
|
178
|
+
function record(value) {
|
|
179
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
180
|
+
}
|
|
181
|
+
/** Source data only; callers must already hold genuine producer evidence admission. */
|
|
182
|
+
function readContextMetadata(stateDir, producer) {
|
|
183
|
+
const root = assertOwnedRunStateDirectory(stateDir, producer.run);
|
|
184
|
+
const path = join(root, "run.json");
|
|
185
|
+
const before = optionalStamp(path);
|
|
186
|
+
if (!before?.isFile() || before.size > BigInt(RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES))
|
|
187
|
+
return { state: "unavailable", reason: "unavailable" };
|
|
188
|
+
const read = readJsonFileResilient(path, undefined, { maxBytes: RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES });
|
|
189
|
+
const after = optionalStamp(path);
|
|
190
|
+
if (!after || !sameFile(before, after))
|
|
191
|
+
return { state: "unavailable", reason: "changed" };
|
|
192
|
+
const meta = record(read.value);
|
|
193
|
+
if (read.diagnostics.length || !meta)
|
|
194
|
+
return { state: "unavailable", reason: "invalid" };
|
|
195
|
+
if (meta.run !== producer.run || meta.run_instance_id !== producer.runInstanceId || meta.ownerId !== producer.ownerId ||
|
|
196
|
+
meta.state_dir !== root)
|
|
197
|
+
return { state: "unavailable", reason: "changed" };
|
|
198
|
+
return { state: "available", root, path, stamp: after, meta };
|
|
199
|
+
}
|
|
200
|
+
/** Readonly current-frame observation, never producer or body permission; callers already hold evidence admission. */
|
|
201
|
+
function captureContextMetadataFence(metadata, producer) {
|
|
202
|
+
const owner = readRunContextIdentity(metadata.root, { run: producer.run, run_instance_id: producer.runInstanceId,
|
|
203
|
+
...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) });
|
|
204
|
+
if (owner.state !== "available" || owner.local_path !== metadata.root)
|
|
205
|
+
return undefined;
|
|
206
|
+
const unchanged = () => {
|
|
207
|
+
try {
|
|
208
|
+
return owner.unchanged() && sameFile(metadata.stamp, lstatSync(metadata.path, { bigint: true }));
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
return unchanged() ? unchanged : undefined;
|
|
215
|
+
}
|
|
216
|
+
/** Checked fixed-source I/O only. Each caller must already admit its producer, projection policy and derived target. */
|
|
217
|
+
function retainContextSource(metadata, path, content, maxBytes, label) {
|
|
218
|
+
const root = lstatSync(metadata.root, { bigint: true });
|
|
219
|
+
const markerPath = join(metadata.root, RUN_STATE_OWNERSHIP_FILE);
|
|
220
|
+
const marker = lstatSync(markerPath, { bigint: true });
|
|
221
|
+
const evidencePath = join(metadata.root, "evidence");
|
|
222
|
+
let evidence = optionalStamp(evidencePath);
|
|
223
|
+
if (evidence && (!evidence.isDirectory() || evidence.isSymbolicLink()))
|
|
224
|
+
throw new ContextExportBindingRefusal(`${label} directory binding is invalid.`);
|
|
225
|
+
if (!evidence) {
|
|
226
|
+
mkdirSync(evidencePath);
|
|
227
|
+
evidence = lstatSync(evidencePath, { bigint: true });
|
|
228
|
+
}
|
|
229
|
+
const frameUnchanged = () => sameObject(root, lstatSync(metadata.root, { bigint: true })) &&
|
|
230
|
+
sameFile(metadata.stamp, lstatSync(metadata.path, { bigint: true })) && sameFile(marker, lstatSync(markerPath, { bigint: true })) &&
|
|
231
|
+
sameObject(evidence, lstatSync(evidencePath, { bigint: true }));
|
|
232
|
+
if (!frameUnchanged())
|
|
233
|
+
throw new ContextExportBindingRefusal(`${label} publication binding changed.`);
|
|
234
|
+
if (optionalStamp(path)) {
|
|
235
|
+
if (readFixedSourceText(path, maxBytes, `${label} source`).text !== content) {
|
|
236
|
+
throw new ContextExportBindingRefusal(`${label} identity already fixes different bytes.`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// Source, view and index remain separate effects; preserve partial/orphan writes on failure.
|
|
241
|
+
writeFileSync(path, content, { flag: "wx", mode: 0o600 });
|
|
242
|
+
}
|
|
243
|
+
const saved = readFixedSourceText(path, maxBytes, `${label} source`);
|
|
244
|
+
if (saved.text !== content || !frameUnchanged())
|
|
245
|
+
throw new ContextExportBindingRefusal(`${label} publication binding changed.`);
|
|
246
|
+
return { unchanged: () => { try {
|
|
247
|
+
return frameUnchanged() && sameFile(saved.stamp, lstatSync(path, { bigint: true }));
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return false;
|
|
251
|
+
} } };
|
|
252
|
+
}
|
|
253
|
+
function projectCapturedRecipe(meta) {
|
|
254
|
+
const contexts = meta.recipe_context_records;
|
|
255
|
+
if (contexts !== undefined && (!Array.isArray(contexts) || contexts.some(value => !record(value))))
|
|
256
|
+
return recipeUnavailable("recipe_metadata_invalid");
|
|
257
|
+
const template = meta.template;
|
|
258
|
+
if (template !== undefined && !(typeof template === "string" && template.length > 0) &&
|
|
259
|
+
!(Array.isArray(template) && template.length > 0) && !record(template))
|
|
260
|
+
return recipeUnavailable("recipe_metadata_invalid");
|
|
261
|
+
const recipe = projectRunRecipe(meta, [], createRunRecipeBodyRedactor(meta));
|
|
262
|
+
if (template === undefined && !recipe.definition)
|
|
263
|
+
return recipeUnavailable("recipe_capture_absent");
|
|
264
|
+
const clock = readRunSupervisionClock(meta);
|
|
265
|
+
if (clock && !createContextIdentityPrivacyCheck(meta)({ started_at: clock.started_at }))
|
|
266
|
+
return recipeUnavailable("recipe_metadata_redacted");
|
|
267
|
+
return { status: "projected", capture: recipe.definition ? "definition_and_launch" : "launch_only", recipe, ...(clock ? { stored_clock: clock } : {}) };
|
|
268
|
+
}
|
|
269
|
+
function captureRecipe(stateDir, producer) {
|
|
270
|
+
const metadata = readContextMetadata(stateDir, producer);
|
|
271
|
+
if (metadata.state === "unavailable")
|
|
272
|
+
return recipeUnavailable(metadata.reason === "changed" ? "recipe_metadata_changed"
|
|
273
|
+
: metadata.reason === "invalid" ? "recipe_metadata_invalid" : "recipe_metadata_unavailable");
|
|
274
|
+
const unchanged = captureContextMetadataFence(metadata, producer);
|
|
275
|
+
if (!unchanged)
|
|
276
|
+
return recipeUnavailable("recipe_metadata_changed");
|
|
277
|
+
// Live metadata fences the observation; it cannot renew static launch selection or its privacy policy.
|
|
278
|
+
const meta = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
279
|
+
if (!meta)
|
|
280
|
+
return recipeUnavailable("recipe_admission_unavailable");
|
|
281
|
+
const metadataUnredacted = createContextIdentityPrivacyCheck(meta);
|
|
282
|
+
if (!metadataUnredacted({ run: producer.run, run_instance_id: producer.runInstanceId }))
|
|
283
|
+
return recipeUnavailable("recipe_identity_redacted");
|
|
284
|
+
const projected = projectCapturedRecipe(meta);
|
|
285
|
+
if (projected.status === "unavailable")
|
|
286
|
+
return projected;
|
|
287
|
+
const { status: _status, ...fields } = projected;
|
|
288
|
+
const capturedAt = new Date().toISOString();
|
|
289
|
+
if (!metadataUnredacted({ captured_at: capturedAt }))
|
|
290
|
+
return recipeUnavailable("recipe_metadata_redacted");
|
|
291
|
+
const content = JSON.stringify({ schema: VIEW_CONFIG.recipe.schema, run: producer.run, run_instance_id: producer.runInstanceId,
|
|
292
|
+
captured_at: capturedAt, coverage: RECIPE_VIEW_COVERAGE, ...fields }, null, 2) + "\n";
|
|
293
|
+
if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_RECIPE_MAX_BYTES)
|
|
294
|
+
return recipeUnavailable("recipe_view_too_large");
|
|
295
|
+
return unchanged() ? Object.freeze({ status: "captured", captured_at: capturedAt, content, unchanged }) : recipeUnavailable("recipe_metadata_changed");
|
|
296
|
+
}
|
|
297
|
+
function recipeReadUnavailable(reason) { return Object.freeze({ state: "unavailable", reason }); }
|
|
298
|
+
/** Checked-source observation only; each private caller already owns its original projection/publication admission. */
|
|
299
|
+
function readSavedContextViewSource(stateDir, identity, role) {
|
|
300
|
+
const owner = readRunContextIdentity(stateDir, identity);
|
|
301
|
+
if (owner.state !== "available")
|
|
302
|
+
return owner;
|
|
303
|
+
let reason = "index_unavailable";
|
|
304
|
+
try {
|
|
305
|
+
const root = owner.local_path, indexPath = join(root, RUN_CONTEXT_INDEX_FILE), index = optionalStamp(indexPath);
|
|
306
|
+
if (!index?.isFile())
|
|
307
|
+
return { state: "unavailable", reason: "index_unavailable" };
|
|
308
|
+
const admitted = readRunContextIndex(root, owner.identity);
|
|
309
|
+
if (admitted.state !== "available")
|
|
310
|
+
return { state: "unavailable", reason: admitted.reason === "observation_changed" ? "observation_changed" : "index_unavailable" };
|
|
311
|
+
const entry = admitted.index.entries[role], config = VIEW_CONFIG[role];
|
|
312
|
+
if (entry.availability !== "available" || entry.mutable !== true || !isDeepStrictEqual(entry.coverage, config.coverage))
|
|
313
|
+
return { state: "unavailable", reason: "role_unavailable" };
|
|
314
|
+
reason = "view_unavailable";
|
|
315
|
+
const evidencePath = join(root, "evidence"), evidence = lstatSync(evidencePath, { bigint: true });
|
|
316
|
+
if (!evidence.isDirectory() || evidence.isSymbolicLink())
|
|
317
|
+
return { state: "unavailable", reason: "view_unavailable" };
|
|
318
|
+
const path = runContextExportPath(role), viewPath = join(root, path);
|
|
319
|
+
const captured = readFixedSourceText(viewPath, config.maxBytes, "Run " + role + " saved view");
|
|
320
|
+
const unchanged = () => {
|
|
321
|
+
try {
|
|
322
|
+
return owner.unchanged() && sameFile(index, lstatSync(indexPath, { bigint: true })) &&
|
|
323
|
+
sameObject(evidence, lstatSync(evidencePath, { bigint: true })) && sameFile(captured.stamp, lstatSync(viewPath, { bigint: true }));
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
if (!unchanged())
|
|
330
|
+
return { state: "unavailable", reason: "observation_changed" };
|
|
331
|
+
if (Number(captured.stamp.size) !== entry.bytes_total)
|
|
332
|
+
return { state: "unavailable", reason: "view_invalid" };
|
|
333
|
+
return { state: "available", identity: owner.identity, path, text: captured.text, bytes_total: Number(captured.stamp.size), unchanged };
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
return { state: "unavailable", reason };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function freezeContextValue(value) {
|
|
340
|
+
if (value && typeof value === "object") {
|
|
341
|
+
Object.values(value).forEach(freezeContextValue);
|
|
342
|
+
Object.freeze(value);
|
|
343
|
+
}
|
|
344
|
+
return value;
|
|
345
|
+
}
|
|
346
|
+
/** Kernel-local original data correspondence, not file-issuer authentication or work/caller/restart authority. */
|
|
347
|
+
export function readRunRecipeContextView(stateDir, producer) {
|
|
348
|
+
let original;
|
|
349
|
+
try {
|
|
350
|
+
original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return recipeReadUnavailable("admission_invalid");
|
|
354
|
+
}
|
|
355
|
+
if (!original)
|
|
356
|
+
return recipeReadUnavailable("admission_unavailable");
|
|
357
|
+
if (typeof stateDir !== "string" || !isAbsolute(stateDir) || stateDir !== original.state_dir || original.run !== producer.run ||
|
|
358
|
+
original.run_instance_id !== producer.runInstanceId || original.ownerId !== producer.ownerId)
|
|
359
|
+
return recipeReadUnavailable("identity_mismatch");
|
|
360
|
+
const identity = { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) };
|
|
361
|
+
const metadataUnredacted = createContextIdentityPrivacyCheck(original);
|
|
362
|
+
if (!metadataUnredacted(identity))
|
|
363
|
+
return recipeReadUnavailable("identity_redacted");
|
|
364
|
+
let projected;
|
|
365
|
+
try {
|
|
366
|
+
projected = projectCapturedRecipe(original);
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return recipeReadUnavailable("admission_unavailable");
|
|
370
|
+
}
|
|
371
|
+
if (projected.status === "unavailable")
|
|
372
|
+
return recipeReadUnavailable("admission_unavailable");
|
|
373
|
+
const { status: _status, ...expected } = projected;
|
|
374
|
+
const source = readSavedContextViewSource(stateDir, identity, "recipe");
|
|
375
|
+
if (source.state !== "available")
|
|
376
|
+
return recipeReadUnavailable(source.reason === "role_unavailable" ? "recipe_unavailable" : source.reason);
|
|
377
|
+
try {
|
|
378
|
+
const view = record(JSON.parse(source.text));
|
|
379
|
+
if (!view || !contextFields(view, ["schema", "run", "run_instance_id", "captured_at", "coverage", "capture", "recipe", "stored_clock"]) ||
|
|
380
|
+
view.schema !== VIEW_CONFIG.recipe.schema || view.run !== source.identity.run || view.run_instance_id !== source.identity.run_instance_id ||
|
|
381
|
+
!contextCoverage(view.coverage, true) || typeof view.captured_at !== "string" || view.captured_at.length > 32 ||
|
|
382
|
+
!Number.isFinite(Date.parse(view.captured_at)) || new Date(view.captured_at).toISOString() !== view.captured_at)
|
|
383
|
+
return recipeReadUnavailable("view_invalid");
|
|
384
|
+
const { schema: _schema, run: _run, run_instance_id: _generation, captured_at: _capturedAt, coverage: _coverage, ...fields } = view;
|
|
385
|
+
if (!isDeepStrictEqual(fields, expected))
|
|
386
|
+
return recipeReadUnavailable("view_mismatch");
|
|
387
|
+
if (!metadataUnredacted({ captured_at: view.captured_at }))
|
|
388
|
+
return recipeReadUnavailable("view_metadata_redacted");
|
|
389
|
+
const result = Object.freeze({ state: "available", basis: "original_producer_projection_match",
|
|
390
|
+
identity: source.identity, path: source.path, media_type: "application/json", bytes_total: source.bytes_total, mutable: true,
|
|
391
|
+
view: freezeContextValue(view), unchanged: source.unchanged });
|
|
392
|
+
if (Buffer.byteLength(JSON.stringify(result), "utf8") > RUN_CONTEXT_RECIPE_MAX_BYTES)
|
|
393
|
+
return recipeReadUnavailable("response_too_large");
|
|
394
|
+
return source.unchanged() ? result : recipeReadUnavailable("observation_changed");
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return recipeReadUnavailable("view_invalid");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function controlReadUnavailable(reason) { return Object.freeze({ state: "unavailable", reason }); }
|
|
401
|
+
/** Recorded Control data only; neither current readiness/capacity nor authority to admit, dispatch or stop work. */
|
|
402
|
+
export function readRunControlContextView(stateDir, producer) {
|
|
403
|
+
let original;
|
|
404
|
+
try {
|
|
405
|
+
original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
return controlReadUnavailable("admission_invalid");
|
|
409
|
+
}
|
|
410
|
+
if (!original)
|
|
411
|
+
return controlReadUnavailable("admission_unavailable");
|
|
412
|
+
if (typeof stateDir !== "string" || !isAbsolute(stateDir) || stateDir !== original.state_dir || original.run !== producer.run ||
|
|
413
|
+
original.run_instance_id !== producer.runInstanceId || original.ownerId !== producer.ownerId)
|
|
414
|
+
return controlReadUnavailable("identity_mismatch");
|
|
415
|
+
const identity = { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) };
|
|
416
|
+
const metadataUnredacted = createContextIdentityPrivacyCheck(original);
|
|
417
|
+
if (!metadataUnredacted(identity))
|
|
418
|
+
return controlReadUnavailable("identity_redacted");
|
|
419
|
+
const publication = controlPublications.get(producer);
|
|
420
|
+
if (!publication)
|
|
421
|
+
return controlReadUnavailable("publication_unavailable");
|
|
422
|
+
if (publication.root !== stateDir)
|
|
423
|
+
return controlReadUnavailable("identity_mismatch");
|
|
424
|
+
const source = readSavedContextViewSource(stateDir, identity, "control");
|
|
425
|
+
if (source.state !== "available")
|
|
426
|
+
return controlReadUnavailable(source.reason === "role_unavailable" ? "control_unavailable" : source.reason);
|
|
427
|
+
const unchanged = () => controlPublications.get(producer) === publication && source.unchanged();
|
|
428
|
+
if (!unchanged())
|
|
429
|
+
return controlReadUnavailable("observation_changed");
|
|
430
|
+
if (source.text !== publication.content)
|
|
431
|
+
return controlReadUnavailable("view_mismatch");
|
|
432
|
+
try {
|
|
433
|
+
const view = JSON.parse(source.text);
|
|
434
|
+
if (!metadataUnredacted({ captured_at: view.captured_at }))
|
|
435
|
+
return controlReadUnavailable("view_metadata_redacted");
|
|
436
|
+
freezeContextValue(view);
|
|
437
|
+
const result = Object.freeze({ state: "available", basis: "original_control_publication_match",
|
|
438
|
+
identity: source.identity, path: source.path, media_type: "application/json", bytes_total: source.bytes_total, mutable: true, view, unchanged });
|
|
439
|
+
if (Buffer.byteLength(JSON.stringify(result), "utf8") > RUN_CONTEXT_CONTROL_MAX_BYTES)
|
|
440
|
+
return controlReadUnavailable("response_too_large");
|
|
441
|
+
return unchanged() ? result : controlReadUnavailable("observation_changed");
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return controlReadUnavailable("view_invalid");
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function executionReadUnavailable(reason) { return Object.freeze({ state: "unavailable", reason }); }
|
|
448
|
+
/** Historical admitted metadata only: recorded references/exits/killed facts grant no body, lifecycle or native authority. */
|
|
449
|
+
export function readRunExecutionContextView(stateDir, producer) {
|
|
450
|
+
let original;
|
|
451
|
+
try {
|
|
452
|
+
original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
return executionReadUnavailable("admission_invalid");
|
|
456
|
+
}
|
|
457
|
+
if (!original)
|
|
458
|
+
return executionReadUnavailable("admission_unavailable");
|
|
459
|
+
if (typeof stateDir !== "string" || !isAbsolute(stateDir) || stateDir !== original.state_dir || original.run !== producer.run ||
|
|
460
|
+
original.run_instance_id !== producer.runInstanceId || original.ownerId !== producer.ownerId)
|
|
461
|
+
return executionReadUnavailable("identity_mismatch");
|
|
462
|
+
const identity = { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) };
|
|
463
|
+
const metadataUnredacted = createContextIdentityPrivacyCheck(original);
|
|
464
|
+
if (!metadataUnredacted(identity))
|
|
465
|
+
return executionReadUnavailable("identity_redacted");
|
|
466
|
+
const publication = executionPublications.get(producer);
|
|
467
|
+
if (!publication)
|
|
468
|
+
return executionReadUnavailable("publication_unavailable");
|
|
469
|
+
if (publication.root !== stateDir)
|
|
470
|
+
return executionReadUnavailable("identity_mismatch");
|
|
471
|
+
const source = readSavedContextViewSource(stateDir, identity, "execution");
|
|
472
|
+
if (source.state !== "available")
|
|
473
|
+
return executionReadUnavailable(source.reason === "role_unavailable" ? "execution_unavailable" : source.reason);
|
|
474
|
+
const unchanged = () => executionPublications.get(producer) === publication && source.unchanged();
|
|
475
|
+
if (!unchanged())
|
|
476
|
+
return executionReadUnavailable("observation_changed");
|
|
477
|
+
if (source.text !== publication.content)
|
|
478
|
+
return executionReadUnavailable("view_mismatch");
|
|
479
|
+
try {
|
|
480
|
+
const view = JSON.parse(source.text);
|
|
481
|
+
if (!metadataUnredacted({ captured_at: view.captured_at }))
|
|
482
|
+
return executionReadUnavailable("view_metadata_redacted");
|
|
483
|
+
freezeContextValue(view);
|
|
484
|
+
const result = Object.freeze({ state: "available", basis: "original_execution_publication_match",
|
|
485
|
+
identity: source.identity, path: source.path, media_type: "application/json", bytes_total: source.bytes_total, mutable: true, view, unchanged });
|
|
486
|
+
if (Buffer.byteLength(JSON.stringify(result), "utf8") > RUN_CONTEXT_EXECUTION_MAX_BYTES)
|
|
487
|
+
return executionReadUnavailable("response_too_large");
|
|
488
|
+
return unchanged() ? result : executionReadUnavailable("observation_changed");
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
return executionReadUnavailable("view_invalid");
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function resultReadUnavailable(reason) { return Object.freeze({ state: "unavailable", reason }); }
|
|
495
|
+
/** Original successful Result-view data only, never current outcome/source/body, active terminal or cleanup authority. */
|
|
496
|
+
export function readRunResultContextView(stateDir, producer) {
|
|
497
|
+
let original;
|
|
498
|
+
try {
|
|
499
|
+
original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
500
|
+
}
|
|
501
|
+
catch {
|
|
502
|
+
return resultReadUnavailable("admission_invalid");
|
|
503
|
+
}
|
|
504
|
+
if (!original)
|
|
505
|
+
return resultReadUnavailable("admission_unavailable");
|
|
506
|
+
if (typeof stateDir !== "string" || !isAbsolute(stateDir) || stateDir !== original.state_dir || original.run !== producer.run ||
|
|
507
|
+
original.run_instance_id !== producer.runInstanceId || original.ownerId !== producer.ownerId)
|
|
508
|
+
return resultReadUnavailable("identity_mismatch");
|
|
509
|
+
const identity = { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) };
|
|
510
|
+
const metadataUnredacted = createContextIdentityPrivacyCheck(original);
|
|
511
|
+
if (!metadataUnredacted(identity))
|
|
512
|
+
return resultReadUnavailable("identity_redacted");
|
|
513
|
+
const publication = resultPublications.get(producer);
|
|
514
|
+
if (!publication)
|
|
515
|
+
return resultReadUnavailable("publication_unavailable");
|
|
516
|
+
if (publication.root !== stateDir)
|
|
517
|
+
return resultReadUnavailable("identity_mismatch");
|
|
518
|
+
const source = readSavedContextViewSource(stateDir, identity, "result");
|
|
519
|
+
if (source.state !== "available")
|
|
520
|
+
return resultReadUnavailable(source.reason === "role_unavailable" ? "result_unavailable" : source.reason);
|
|
521
|
+
const unchanged = () => resultPublications.get(producer) === publication && source.unchanged();
|
|
522
|
+
if (!unchanged())
|
|
523
|
+
return resultReadUnavailable("observation_changed");
|
|
524
|
+
if (source.text !== publication.content)
|
|
525
|
+
return resultReadUnavailable("view_mismatch");
|
|
526
|
+
try {
|
|
527
|
+
const view = JSON.parse(source.text);
|
|
528
|
+
if (!metadataUnredacted({ captured_at: view.captured_at }))
|
|
529
|
+
return resultReadUnavailable("view_metadata_redacted");
|
|
530
|
+
freezeContextValue(view);
|
|
531
|
+
const result = Object.freeze({ state: "available", basis: "original_result_publication_match",
|
|
532
|
+
identity: source.identity, path: source.path, media_type: "application/json", bytes_total: source.bytes_total, mutable: true, view, unchanged });
|
|
533
|
+
if (Buffer.byteLength(JSON.stringify(result), "utf8") > RUN_CONTEXT_RESULT_MAX_BYTES)
|
|
534
|
+
return resultReadUnavailable("response_too_large");
|
|
535
|
+
return unchanged() ? result : resultReadUnavailable("observation_changed");
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
return resultReadUnavailable("view_invalid");
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function traceReadUnavailable(reason) { return Object.freeze({ state: "unavailable", reason }); }
|
|
542
|
+
/** Reads original successful publication data, never a live journal projection or a reconstructed binding. */
|
|
543
|
+
export function readRunTraceContextView(stateDir, producer) {
|
|
544
|
+
let original;
|
|
545
|
+
try {
|
|
546
|
+
original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
return traceReadUnavailable("admission_invalid");
|
|
550
|
+
}
|
|
551
|
+
if (!original)
|
|
552
|
+
return traceReadUnavailable("admission_unavailable");
|
|
553
|
+
if (typeof stateDir !== "string" || !isAbsolute(stateDir) || stateDir !== original.state_dir || original.run !== producer.run ||
|
|
554
|
+
original.run_instance_id !== producer.runInstanceId || original.ownerId !== producer.ownerId)
|
|
555
|
+
return traceReadUnavailable("identity_mismatch");
|
|
556
|
+
const identity = { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) };
|
|
557
|
+
const metadataUnredacted = createContextIdentityPrivacyCheck(original);
|
|
558
|
+
if (!metadataUnredacted(identity))
|
|
559
|
+
return traceReadUnavailable("identity_redacted");
|
|
560
|
+
const publication = tracePublications.get(producer);
|
|
561
|
+
if (!publication)
|
|
562
|
+
return traceReadUnavailable("publication_unavailable");
|
|
563
|
+
if (publication.root !== stateDir)
|
|
564
|
+
return traceReadUnavailable("identity_mismatch");
|
|
565
|
+
const source = readSavedContextViewSource(stateDir, identity, "trace");
|
|
566
|
+
if (source.state !== "available")
|
|
567
|
+
return traceReadUnavailable(source.reason === "role_unavailable" ? "trace_unavailable" : source.reason);
|
|
568
|
+
const unchanged = () => tracePublications.get(producer) === publication && source.unchanged();
|
|
569
|
+
if (!unchanged())
|
|
570
|
+
return traceReadUnavailable("observation_changed");
|
|
571
|
+
if (source.text !== publication.content)
|
|
572
|
+
return traceReadUnavailable("view_mismatch");
|
|
573
|
+
try {
|
|
574
|
+
// Exact canonical JSONL binding precedes parse/freeze. A schema flag grants no authority.
|
|
575
|
+
const [header, ...items] = source.text.trimEnd().split("\n").map(line => JSON.parse(line));
|
|
576
|
+
if (!metadataUnredacted({ captured_at: header.captured_at }))
|
|
577
|
+
return traceReadUnavailable("view_metadata_redacted");
|
|
578
|
+
const view = freezeContextValue({ header, items });
|
|
579
|
+
const result = Object.freeze({ state: "available", basis: "original_trace_publication_match",
|
|
580
|
+
identity: source.identity, path: source.path, media_type: "application/x-ndjson", bytes_total: source.bytes_total, mutable: true, view, unchanged });
|
|
581
|
+
if (Buffer.byteLength(JSON.stringify(result), "utf8") > RUN_CONTEXT_TRACE_MAX_BYTES)
|
|
582
|
+
return traceReadUnavailable("response_too_large");
|
|
583
|
+
return unchanged() ? result : traceReadUnavailable("observation_changed");
|
|
584
|
+
}
|
|
585
|
+
catch {
|
|
586
|
+
return traceReadUnavailable("view_invalid");
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function captureTrace(stateDir, producer) {
|
|
590
|
+
const root = assertOwnedRunStateDirectory(stateDir, producer.run);
|
|
591
|
+
const path = runTraceFile(root);
|
|
592
|
+
const before = optionalStamp(path);
|
|
593
|
+
if (!before)
|
|
594
|
+
return traceUnavailable("trace_journal_absent");
|
|
595
|
+
if (!before.isFile() || before.size > BigInt(TRACE_JOURNAL_MAX_BYTES))
|
|
596
|
+
return traceUnavailable("trace_journal_unavailable");
|
|
597
|
+
const metadata = readContextMetadata(root, producer);
|
|
598
|
+
if (metadata.state === "unavailable")
|
|
599
|
+
return traceUnavailable(metadata.reason === "changed" ? "trace_metadata_changed" : "trace_metadata_unavailable");
|
|
600
|
+
const metadataUnchanged = captureContextMetadataFence(metadata, producer);
|
|
601
|
+
if (!metadataUnchanged)
|
|
602
|
+
return traceUnavailable("trace_metadata_changed");
|
|
603
|
+
// Journal contents stay current; their Recipe privacy cannot be renewed from live metadata.
|
|
604
|
+
const recipe = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
605
|
+
if (!recipe)
|
|
606
|
+
return traceUnavailable("trace_admission_unavailable");
|
|
607
|
+
const identityUnchanged = createContextIdentityPrivacyCheck(recipe);
|
|
608
|
+
if (!identityUnchanged({ run: producer.run, run_instance_id: producer.runInstanceId }))
|
|
609
|
+
return traceUnavailable("trace_identity_redacted");
|
|
610
|
+
const journal = readRunTraceJournal(root);
|
|
611
|
+
const after = optionalStamp(path);
|
|
612
|
+
const metaAfter = optionalStamp(metadata.path);
|
|
613
|
+
if (!after || !sameFile(before, after))
|
|
614
|
+
return traceUnavailable("trace_journal_changed");
|
|
615
|
+
if (!metaAfter || !sameFile(metadata.stamp, metaAfter) || !metadataUnchanged())
|
|
616
|
+
return traceUnavailable("trace_metadata_changed");
|
|
617
|
+
if (journal.diagnostics.length || journal.omittedPrefixBytes > 0)
|
|
618
|
+
return traceUnavailable("trace_journal_invalid");
|
|
619
|
+
// Source identity/time and routing tokens stay exact; omit unsafe whole items in the existing bounded source window.
|
|
620
|
+
const items = projectRunTraceJournal(journal, { limit: DEFAULT_INSPECT_LINES, recipe, redactEvidence: createRunRecipeBodyRedactor(recipe) })
|
|
621
|
+
.filter(item => identityUnchanged({ id: item.id, ts: item.ts, kind: item.kind }));
|
|
622
|
+
const capturedAt = new Date().toISOString();
|
|
623
|
+
if (!identityUnchanged({ captured_at: capturedAt }))
|
|
624
|
+
return traceUnavailable("trace_metadata_redacted");
|
|
625
|
+
const summary = summarizeRunTraceJournal(journal);
|
|
626
|
+
const encode = () => [JSON.stringify({ schema: VIEW_CONFIG.trace.schema, run: producer.run,
|
|
627
|
+
run_instance_id: producer.runInstanceId, captured_at: capturedAt, source_scope: "canonical_journal", order: "newest_first",
|
|
628
|
+
coverage: TRACE_VIEW_COVERAGE, journal: summary, read_omitted_events: journal.omittedEvents,
|
|
629
|
+
events_total: journal.events.length, events_shown: items.length, events_omitted: journal.events.length - items.length }),
|
|
630
|
+
...items.map(item => JSON.stringify(item))].join("\n") + "\n";
|
|
631
|
+
let content = encode();
|
|
632
|
+
while (items.length && Buffer.byteLength(content, "utf8") > RUN_CONTEXT_TRACE_MAX_BYTES) {
|
|
633
|
+
items.pop();
|
|
634
|
+
content = encode();
|
|
635
|
+
}
|
|
636
|
+
if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_TRACE_MAX_BYTES)
|
|
637
|
+
return traceUnavailable("trace_journal_unavailable");
|
|
638
|
+
const unchanged = () => {
|
|
639
|
+
try {
|
|
640
|
+
return metadataUnchanged() && sameFile(after, lstatSync(path, { bigint: true }));
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
if (!metadataUnchanged())
|
|
647
|
+
return traceUnavailable("trace_metadata_changed");
|
|
648
|
+
return unchanged() ? Object.freeze({ status: "captured", captured_at: capturedAt, content, unchanged, original_root: metadata.root }) : traceUnavailable("trace_journal_changed");
|
|
649
|
+
}
|
|
650
|
+
function captureResult(stateDir, producer) {
|
|
651
|
+
const metadata = readContextMetadata(stateDir, producer);
|
|
652
|
+
if (metadata.state === "unavailable")
|
|
653
|
+
return resultUnavailable(metadata.reason === "changed" ? "result_changed" : "result_metadata_unavailable");
|
|
654
|
+
const captured = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
655
|
+
if (!captured)
|
|
656
|
+
return resultUnavailable("result_metadata_unavailable");
|
|
657
|
+
const metadataUnredacted = createContextIdentityPrivacyCheck(captured);
|
|
658
|
+
if (!metadataUnredacted({ run: producer.run, run_instance_id: producer.runInstanceId,
|
|
659
|
+
...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) }))
|
|
660
|
+
return resultUnavailable("result_identity_redacted");
|
|
661
|
+
const path = join(metadata.root, "result.json");
|
|
662
|
+
const before = optionalStamp(path);
|
|
663
|
+
if (!before)
|
|
664
|
+
return resultUnavailable("result_absent");
|
|
665
|
+
if (!before.isFile() || before.size > BigInt(RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES))
|
|
666
|
+
return resultUnavailable("result_unavailable");
|
|
667
|
+
const snapshot = readRunNaturalOutcomeSnapshot(metadata.root, metadata.meta, resultPath => {
|
|
668
|
+
const read = readJsonFileResilient(resultPath, undefined, { maxBytes: RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES });
|
|
669
|
+
return read.diagnostics.length ? undefined : read.value;
|
|
670
|
+
});
|
|
671
|
+
const after = optionalStamp(path);
|
|
672
|
+
const metaAfter = optionalStamp(metadata.path);
|
|
673
|
+
if (!after || !sameFile(before, after) || !metaAfter || !sameFile(metadata.stamp, metaAfter))
|
|
674
|
+
return resultUnavailable("result_changed");
|
|
675
|
+
if (!snapshot.result || (snapshot.outcome.state !== "accepted" && snapshot.outcome.state !== "captured"))
|
|
676
|
+
return resultUnavailable("result_invalid");
|
|
677
|
+
const result = snapshot.result;
|
|
678
|
+
if (!metadataUnredacted({ recorded_at: result.completedAt }))
|
|
679
|
+
return resultUnavailable("result_metadata_redacted");
|
|
680
|
+
const projected = projectDiagnosticMetadata(producer, result, createRunRecipeTextRedactor(captured));
|
|
681
|
+
if (projected.state === "unavailable")
|
|
682
|
+
return resultUnavailable(projected.reason);
|
|
683
|
+
const { details, sourceContent } = projected;
|
|
684
|
+
const hasDetails = Object.keys(details).length > 0;
|
|
685
|
+
const sourcePath = resultDiagnosticsPath(metadata.root, { run: producer.run, run_instance_id: producer.runInstanceId, ownerId: producer.ownerId });
|
|
686
|
+
const source = hasDetails ? { path: sourcePath, run_instance_id: producer.runInstanceId,
|
|
687
|
+
sha256: createHash("sha256").update(sourceContent).digest("hex") } : undefined;
|
|
688
|
+
if (source && !metadataUnredacted(source))
|
|
689
|
+
return resultUnavailable("result_metadata_redacted");
|
|
690
|
+
const evidence = {};
|
|
691
|
+
const fields = {};
|
|
692
|
+
for (const [key, text] of Object.entries(details)) {
|
|
693
|
+
const { text: preview, ...counts } = diagnosticExcerpt(text);
|
|
694
|
+
evidence[key] = preview;
|
|
695
|
+
fields[key] = counts;
|
|
696
|
+
}
|
|
697
|
+
const diagnostics = hasDetails ? { state: "available", source, bytes_total: Buffer.byteLength(sourceContent, "utf8"), fields }
|
|
698
|
+
: { state: "not_recorded" };
|
|
699
|
+
const flags = { ...(typeof result.killed === "boolean" ? { reported_killed: result.killed } : {}),
|
|
700
|
+
...(typeof result.truncated === "boolean" ? { reported_truncated: result.truncated } : {}) };
|
|
701
|
+
const capturedAt = new Date().toISOString();
|
|
702
|
+
if (!metadataUnredacted({ captured_at: capturedAt }))
|
|
703
|
+
return resultUnavailable("result_metadata_redacted");
|
|
704
|
+
const content = JSON.stringify({ schema: VIEW_CONFIG.result.schema, run: producer.run, run_instance_id: producer.runInstanceId,
|
|
705
|
+
captured_at: capturedAt, coverage: RESULT_VIEW_COVERAGE, content_scope: "canonical_outcome_metadata", primary_text: "not_included", diagnostics,
|
|
706
|
+
outcome: { acceptance: snapshot.outcome.state === "accepted" ? "natural" : "capture_only",
|
|
707
|
+
...(snapshot.outcome.state === "accepted" ? { terminal_status: snapshot.outcome.status } : {}),
|
|
708
|
+
code: result.code, recorded_at: result.completedAt, ...evidence,
|
|
709
|
+
...(Object.keys(flags).length ? { producer_flags: flags } : {}) } }, null, 2) + "\n";
|
|
710
|
+
if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_RESULT_MAX_BYTES)
|
|
711
|
+
return resultUnavailable("result_view_too_large");
|
|
712
|
+
const resultUnchanged = () => {
|
|
713
|
+
try {
|
|
714
|
+
return sameFile(after, lstatSync(path, { bigint: true })) &&
|
|
715
|
+
sameFile(metadata.stamp, lstatSync(metadata.path, { bigint: true }));
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
if (!resultUnchanged())
|
|
722
|
+
return resultUnavailable("result_changed");
|
|
723
|
+
const retained = hasDetails ? retainContextSource(metadata, sourcePath, sourceContent, RUN_RESULT_DIAGNOSTICS_MAX_BYTES, "Run result diagnostics") : undefined;
|
|
724
|
+
const unchanged = () => resultUnchanged() && (retained?.unchanged() ?? true);
|
|
725
|
+
if (!unchanged())
|
|
726
|
+
throw new ContextExportBindingRefusal("Run result diagnostics publication binding changed.");
|
|
727
|
+
return Object.freeze({ status: "captured", captured_at: capturedAt, content, unchanged, original_root: metadata.root });
|
|
728
|
+
}
|
|
729
|
+
function projectDiagnosticMetadata(producer, result, redact) {
|
|
730
|
+
const details = Object.fromEntries(RESULT_DIAGNOSTIC_FIELDS.flatMap(key => typeof result[key] === "string" ? [[key, redact(result[key])]] : []));
|
|
731
|
+
if (Object.values(details).some(text => Buffer.from(text, "utf8").toString("utf8") !== text))
|
|
732
|
+
return { state: "unavailable", reason: "result_diagnostics_invalid" };
|
|
733
|
+
const sourceContent = JSON.stringify({ schema: RUN_RESULT_DIAGNOSTICS_PROTOCOL, run: producer.run, run_instance_id: producer.runInstanceId,
|
|
734
|
+
content_scope: "canonical_outcome_diagnostic_strings", privacy_scope: "producer_admission", primary_text: "not_included",
|
|
735
|
+
coverage: { truncated: false, redacted: true }, fields: details }, null, 2) + "\n";
|
|
736
|
+
if (Buffer.byteLength(sourceContent, "utf8") > RUN_RESULT_DIAGNOSTICS_MAX_BYTES)
|
|
737
|
+
return { state: "unavailable", reason: "result_diagnostics_too_large" };
|
|
738
|
+
return { state: "available", details, sourceContent };
|
|
739
|
+
}
|
|
740
|
+
function diagnosticsUnavailable(reason) {
|
|
741
|
+
return Object.freeze({ result_diagnostics: null, result_diagnostics_unavailable: reason });
|
|
742
|
+
}
|
|
743
|
+
/** Capture privacy before work. Source I/O occurs only in the genuine synchronous first-result callback, not initialization. */
|
|
744
|
+
export function createRunResultDiagnosticsPublisher(stateDir, producer) {
|
|
745
|
+
const captured = withRunProducerEvidence(stateDir, producer, () => readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES));
|
|
746
|
+
let redact;
|
|
747
|
+
let metadataUnredacted;
|
|
748
|
+
let identityUnchanged = false;
|
|
749
|
+
let attempted = false;
|
|
750
|
+
let failure;
|
|
751
|
+
try {
|
|
752
|
+
if (captured) {
|
|
753
|
+
metadataUnredacted = createContextIdentityPrivacyCheck(captured);
|
|
754
|
+
identityUnchanged = metadataUnredacted({ run: producer.run, run_instance_id: producer.runInstanceId,
|
|
755
|
+
...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) });
|
|
756
|
+
redact = createRunRecipeTextRedactor(captured);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
catch { /* No fixed privacy proof means explicit unavailable metadata. */ }
|
|
760
|
+
return Object.freeze({
|
|
761
|
+
publish: (admission) => {
|
|
762
|
+
try {
|
|
763
|
+
return withRunResultDiagnosticsAdmission(stateDir, producer, admission, (candidate) => {
|
|
764
|
+
if (attempted)
|
|
765
|
+
throw new ContextExportBindingRefusal("Run result diagnostics publication was already attempted.");
|
|
766
|
+
attempted = true;
|
|
767
|
+
if (!redact || !metadataUnredacted)
|
|
768
|
+
return diagnosticsUnavailable("privacy_capture_unavailable");
|
|
769
|
+
if (!identityUnchanged)
|
|
770
|
+
return diagnosticsUnavailable("identity_redacted");
|
|
771
|
+
try {
|
|
772
|
+
const projected = projectDiagnosticMetadata(producer, candidate, redact);
|
|
773
|
+
if (projected.state === "unavailable")
|
|
774
|
+
return diagnosticsUnavailable(projected.reason);
|
|
775
|
+
const fields = RESULT_DIAGNOSTIC_FIELDS.filter(field => Object.hasOwn(projected.details, field));
|
|
776
|
+
if (!fields.length)
|
|
777
|
+
return diagnosticsUnavailable("not_recorded");
|
|
778
|
+
const metadata = readContextMetadata(stateDir, producer);
|
|
779
|
+
if (metadata.state === "unavailable") {
|
|
780
|
+
if (metadata.reason === "changed" || metadata.reason === "invalid")
|
|
781
|
+
throw new ContextExportBindingRefusal("Run result diagnostics publication binding changed.");
|
|
782
|
+
return diagnosticsUnavailable("capture_unavailable");
|
|
783
|
+
}
|
|
784
|
+
const path = resultDiagnosticsPath(metadata.root, { run: producer.run, run_instance_id: producer.runInstanceId, ownerId: producer.ownerId });
|
|
785
|
+
const source = { path, run_instance_id: producer.runInstanceId, sha256: createHash("sha256").update(projected.sourceContent).digest("hex") };
|
|
786
|
+
if (!metadataUnredacted(source))
|
|
787
|
+
return diagnosticsUnavailable("source_reference_redacted");
|
|
788
|
+
const descriptor = { result_diagnostics: { source, bytes_total: Buffer.byteLength(projected.sourceContent, "utf8"), fields } };
|
|
789
|
+
if (Buffer.byteLength(JSON.stringify(descriptor), "utf8") > RUN_DELIVERY_MODEL_MAX_BYTES)
|
|
790
|
+
return diagnosticsUnavailable("descriptor_too_large");
|
|
791
|
+
let binding;
|
|
792
|
+
try {
|
|
793
|
+
binding = decodeRunResultDiagnosticsBinding(metadata.root, producer.runInstanceId, descriptor);
|
|
794
|
+
}
|
|
795
|
+
catch (error) {
|
|
796
|
+
throw new ContextExportBindingRefusal("Run result diagnostics descriptor binding is invalid.", { cause: error });
|
|
797
|
+
}
|
|
798
|
+
const retained = retainContextSource(metadata, path, projected.sourceContent, RUN_RESULT_DIAGNOSTICS_MAX_BYTES, "Run result diagnostics");
|
|
799
|
+
if (!retained.unchanged())
|
|
800
|
+
throw new ContextExportBindingRefusal("Run result diagnostics publication binding changed.");
|
|
801
|
+
return binding;
|
|
802
|
+
}
|
|
803
|
+
catch (error) {
|
|
804
|
+
if (error instanceof ContextExportBindingRefusal || error instanceof PrimaryTextBindingRefusal)
|
|
805
|
+
throw error;
|
|
806
|
+
failure = error;
|
|
807
|
+
return diagnosticsUnavailable("publication_failed");
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
catch (error) {
|
|
812
|
+
if (failure !== undefined && error !== failure)
|
|
813
|
+
throw new AggregateError([failure, error], "Run result diagnostics authority changed after publication failure.");
|
|
814
|
+
throw error;
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
readFailure: () => failure,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
function diagnosticsReadUnavailable(reason) {
|
|
821
|
+
return Object.freeze({ state: "unavailable", reason });
|
|
822
|
+
}
|
|
823
|
+
function resultDiagnosticsPath(root, identity) {
|
|
824
|
+
const key = createHash("sha256").update(JSON.stringify([identity.run, identity.run_instance_id, identity.ownerId ?? null])).digest("hex");
|
|
825
|
+
return join(root, "evidence", `result-diagnostics-${key}.json`);
|
|
826
|
+
}
|
|
827
|
+
function diagnosticExcerpt(text, range, maxChars = RESULT_DIAGNOSTIC_PREVIEW_CHARS) {
|
|
828
|
+
const bytes = Buffer.from(text, "utf8");
|
|
829
|
+
const preview = range ? bytes.subarray(range.start, range.end).toString("utf8")
|
|
830
|
+
: Array.from(text).slice(0, maxChars).join("");
|
|
831
|
+
const total = primaryChars(text);
|
|
832
|
+
const shown = primaryChars(preview);
|
|
833
|
+
return Object.freeze({ text: preview, bytes_total: bytes.length,
|
|
834
|
+
shown_bytes: Object.freeze(range ? { start: range.start, end: range.end } : { start: 0, end: Buffer.byteLength(preview, "utf8") }),
|
|
835
|
+
chars_total: total, chars_shown: shown, chars_omitted: total - shown, truncated: shown < total });
|
|
836
|
+
}
|
|
837
|
+
/** Closed saved projection data only; caller owns source/hash/identity admission and exposure policy. */
|
|
838
|
+
function diagnosticSourceFields(text, identity) {
|
|
839
|
+
const saved = record(JSON.parse(text));
|
|
840
|
+
const fields = record(saved?.fields);
|
|
841
|
+
if (!saved || !contextFields(saved, ["schema", "run", "run_instance_id", "content_scope", "privacy_scope", "primary_text", "coverage", "fields"]) ||
|
|
842
|
+
saved.schema !== RUN_RESULT_DIAGNOSTICS_PROTOCOL || saved.run !== identity.run || saved.run_instance_id !== identity.run_instance_id ||
|
|
843
|
+
saved.content_scope !== "canonical_outcome_diagnostic_strings" || saved.privacy_scope !== "producer_admission" || saved.primary_text !== "not_included" ||
|
|
844
|
+
!contextCoverage(saved.coverage, false) || !fields || !Object.keys(fields).length || !contextFields(fields, RESULT_DIAGNOSTIC_FIELDS) ||
|
|
845
|
+
Object.values(fields).some(value => typeof value !== "string" || Buffer.from(value, "utf8").toString("utf8") !== value))
|
|
846
|
+
return undefined;
|
|
847
|
+
return Object.freeze(fields);
|
|
848
|
+
}
|
|
849
|
+
/** Eligible completion only: exact natural first-result metadata binding, never a view/index or raw-output fallback. */
|
|
850
|
+
export function readRunResultDiagnosticOutcome(stateDir, expected, status) {
|
|
851
|
+
const unavailable = (reason, saved) => Object.freeze({ state: "unavailable", reason,
|
|
852
|
+
...(saved === undefined ? {} : { result_diagnostics_unavailable: saved }) });
|
|
853
|
+
if (status !== "done" && status !== "failed")
|
|
854
|
+
return unavailable("outcome_mismatch");
|
|
855
|
+
const request = contextReadIdentity(expected);
|
|
856
|
+
if (!request)
|
|
857
|
+
return unavailable("selection_invalid");
|
|
858
|
+
const owner = readRunContextIdentity(stateDir, request);
|
|
859
|
+
if (owner.state !== "available")
|
|
860
|
+
return unavailable(owner.reason);
|
|
861
|
+
let reason = "result_unavailable";
|
|
862
|
+
try {
|
|
863
|
+
const root = owner.local_path;
|
|
864
|
+
const metadata = contextJson(join(root, "run.json"));
|
|
865
|
+
const meta = metadata.value;
|
|
866
|
+
if (meta.outcome_protocol !== RUN_OUTCOME_PROTOCOL || !Number.isSafeInteger(meta.pid) || Number(meta.pid) <= 0)
|
|
867
|
+
return unavailable("publication_unavailable");
|
|
868
|
+
const resultPath = join(root, "result.json");
|
|
869
|
+
const canonical = contextJson(resultPath);
|
|
870
|
+
const snapshot = readRunNaturalOutcomeSnapshot(root, meta, () => canonical.value);
|
|
871
|
+
const frameUnchanged = () => {
|
|
872
|
+
try {
|
|
873
|
+
return owner.unchanged() && sameFile(metadata.stamp, lstatSync(join(root, "run.json"), { bigint: true })) &&
|
|
874
|
+
sameFile(canonical.stamp, lstatSync(resultPath, { bigint: true }));
|
|
875
|
+
}
|
|
876
|
+
catch {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
if (!frameUnchanged())
|
|
881
|
+
return unavailable("observation_changed");
|
|
882
|
+
if (snapshot.outcome.state === "captured")
|
|
883
|
+
return unavailable("nonterminal_capture");
|
|
884
|
+
if (!snapshot.result || snapshot.outcome.state !== "accepted")
|
|
885
|
+
return unavailable("result_unavailable");
|
|
886
|
+
if (snapshot.outcome.status !== status)
|
|
887
|
+
return unavailable("outcome_mismatch");
|
|
888
|
+
if (snapshot.result.result_diagnostics_protocol !== RUN_RESULT_DIAGNOSTICS_PROTOCOL)
|
|
889
|
+
return unavailable("publication_unavailable");
|
|
890
|
+
reason = "binding_invalid";
|
|
891
|
+
const binding = decodeRunResultDiagnosticsBinding(root, owner.identity.run_instance_id, snapshot.result);
|
|
892
|
+
const reference = binding.result_diagnostics;
|
|
893
|
+
if (!reference)
|
|
894
|
+
return frameUnchanged() ? unavailable("diagnostics_unavailable", binding.result_diagnostics_unavailable) : unavailable("observation_changed");
|
|
895
|
+
if (reference.source.path !== resultDiagnosticsPath(root, owner.identity) || reference.bytes_total > RUN_RESULT_DIAGNOSTICS_MAX_BYTES)
|
|
896
|
+
return unavailable("binding_invalid");
|
|
897
|
+
reason = "source_unavailable";
|
|
898
|
+
const evidencePath = join(root, "evidence");
|
|
899
|
+
const evidence = lstatSync(evidencePath, { bigint: true });
|
|
900
|
+
if (!evidence.isDirectory() || evidence.isSymbolicLink())
|
|
901
|
+
return unavailable("source_unavailable");
|
|
902
|
+
const retained = readFixedSourceText(reference.source.path, RUN_RESULT_DIAGNOSTICS_MAX_BYTES, "Run result diagnostics source");
|
|
903
|
+
reason = "source_invalid";
|
|
904
|
+
if (Number(retained.stamp.size) !== reference.bytes_total || createHash("sha256").update(retained.text).digest("hex") !== reference.source.sha256)
|
|
905
|
+
return unavailable("source_invalid");
|
|
906
|
+
const fields = diagnosticSourceFields(retained.text, owner.identity);
|
|
907
|
+
if (!fields || JSON.stringify(RESULT_DIAGNOSTIC_FIELDS.filter(key => Object.hasOwn(fields, key))) !== JSON.stringify(reference.fields))
|
|
908
|
+
return unavailable("source_invalid");
|
|
909
|
+
const field = ["error", "failure_reason", "command"].find(key => Object.hasOwn(fields, key));
|
|
910
|
+
const excerpt = decodeRunResultDiagnosticExcerpt(reference, { field, ...diagnosticExcerpt(fields[field], undefined, RUN_RESULT_DIAGNOSTIC_EXCERPT_CHARS) });
|
|
911
|
+
const unchanged = () => {
|
|
912
|
+
try {
|
|
913
|
+
return frameUnchanged() && sameObject(evidence, lstatSync(evidencePath, { bigint: true })) &&
|
|
914
|
+
sameFile(retained.stamp, lstatSync(reference.source.path, { bigint: true }));
|
|
915
|
+
}
|
|
916
|
+
catch {
|
|
917
|
+
return false;
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
return unchanged() ? Object.freeze({ state: "available", identity: owner.identity, binding, excerpt, terminal_status: snapshot.outcome.status, unchanged }) : unavailable("observation_changed");
|
|
921
|
+
}
|
|
922
|
+
catch {
|
|
923
|
+
return unavailable(reason);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
function diagnosticOutcome(value) {
|
|
927
|
+
const outcome = record(value);
|
|
928
|
+
if (!outcome || !contextFields(outcome, ["acceptance", "terminal_status", "code", "recorded_at", "producer_flags", ...RESULT_DIAGNOSTIC_FIELDS]) ||
|
|
929
|
+
typeof outcome.code !== "number" || !Number.isSafeInteger(outcome.code) || typeof outcome.recorded_at !== "string" ||
|
|
930
|
+
!Number.isFinite(Date.parse(outcome.recorded_at)) || new Date(outcome.recorded_at).toISOString() !== outcome.recorded_at ||
|
|
931
|
+
(outcome.acceptance !== "natural" && outcome.acceptance !== "capture_only") ||
|
|
932
|
+
(outcome.acceptance === "natural" ? outcome.terminal_status !== (outcome.code === 0 ? "done" : "failed") : Object.hasOwn(outcome, "terminal_status")))
|
|
933
|
+
return undefined;
|
|
934
|
+
if (outcome.producer_flags !== undefined) {
|
|
935
|
+
const flags = record(outcome.producer_flags);
|
|
936
|
+
if (!flags || !Object.keys(flags).length || !contextFields(flags, ["reported_killed", "reported_truncated"]) ||
|
|
937
|
+
Object.values(flags).some(flag => typeof flag !== "boolean"))
|
|
938
|
+
return undefined;
|
|
939
|
+
}
|
|
940
|
+
if (RESULT_DIAGNOSTIC_FIELDS.some(field => Object.hasOwn(outcome, field) && typeof outcome[field] !== "string"))
|
|
941
|
+
return undefined;
|
|
942
|
+
return outcome;
|
|
943
|
+
}
|
|
944
|
+
/** Fixed saved diagnostic metadata only. The query selects one field and optional decoded-field UTF-8 byte range. */
|
|
945
|
+
export function readRunResultDiagnosticsSource(stateDir, expected, query) {
|
|
946
|
+
const request = contextReadIdentity(expected);
|
|
947
|
+
const selection = contextReadRequest(query);
|
|
948
|
+
if (!request || !selection || !contextFields(selection, ["field", "start", "end"]) ||
|
|
949
|
+
!RESULT_DIAGNOSTIC_FIELDS.includes(selection.field))
|
|
950
|
+
return diagnosticsReadUnavailable("selection_invalid");
|
|
951
|
+
const field = selection.field;
|
|
952
|
+
let range;
|
|
953
|
+
if (Object.hasOwn(selection, "start") || Object.hasOwn(selection, "end")) {
|
|
954
|
+
if (!Number.isSafeInteger(selection.start) || !Number.isSafeInteger(selection.end) || Number(selection.start) < 0 ||
|
|
955
|
+
Number(selection.end) < Number(selection.start))
|
|
956
|
+
return diagnosticsReadUnavailable("selection_invalid");
|
|
957
|
+
range = { start: Number(selection.start), end: Number(selection.end) };
|
|
958
|
+
if (range.end - range.start > RUN_CONTEXT_RESULT_MAX_BYTES)
|
|
959
|
+
return diagnosticsReadUnavailable("range_too_large");
|
|
960
|
+
}
|
|
961
|
+
const owner = readRunContextIdentity(stateDir, request);
|
|
962
|
+
if (owner.state !== "available")
|
|
963
|
+
return diagnosticsReadUnavailable(owner.reason);
|
|
964
|
+
let reason = "index_unavailable";
|
|
965
|
+
try {
|
|
966
|
+
const root = owner.local_path;
|
|
967
|
+
const indexPath = join(root, RUN_CONTEXT_INDEX_FILE);
|
|
968
|
+
const index = optionalStamp(indexPath);
|
|
969
|
+
if (!index?.isFile())
|
|
970
|
+
return diagnosticsReadUnavailable("index_unavailable");
|
|
971
|
+
const admitted = readRunContextIndex(root, owner.identity);
|
|
972
|
+
if (admitted.state !== "available")
|
|
973
|
+
return diagnosticsReadUnavailable(admitted.reason === "observation_changed" ? "observation_changed" : "index_unavailable");
|
|
974
|
+
const entry = admitted.index.entries.result;
|
|
975
|
+
if (entry.availability !== "available" || entry.mutable !== true || !contextCoverage(entry.coverage, true))
|
|
976
|
+
return diagnosticsReadUnavailable("publication_unavailable");
|
|
977
|
+
reason = "view_unavailable";
|
|
978
|
+
const evidencePath = join(root, "evidence");
|
|
979
|
+
const evidence = lstatSync(evidencePath, { bigint: true });
|
|
980
|
+
if (!evidence.isDirectory() || evidence.isSymbolicLink())
|
|
981
|
+
return diagnosticsReadUnavailable("view_unavailable");
|
|
982
|
+
const viewPath = join(root, runContextExportPath("result"));
|
|
983
|
+
const captured = readFixedSourceText(viewPath, RUN_CONTEXT_RESULT_MAX_BYTES, "Run result diagnostics view");
|
|
984
|
+
const frameUnchanged = () => {
|
|
985
|
+
try {
|
|
986
|
+
return owner.unchanged() && sameFile(index, lstatSync(indexPath, { bigint: true })) &&
|
|
987
|
+
sameObject(evidence, lstatSync(evidencePath, { bigint: true })) && sameFile(captured.stamp, lstatSync(viewPath, { bigint: true }));
|
|
988
|
+
}
|
|
989
|
+
catch {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
if (!frameUnchanged())
|
|
994
|
+
return diagnosticsReadUnavailable("observation_changed");
|
|
995
|
+
reason = "view_invalid";
|
|
996
|
+
const view = record(JSON.parse(captured.text));
|
|
997
|
+
if (!view || !contextFields(view, ["schema", "run", "run_instance_id", "captured_at", "coverage", "content_scope", "primary_text", "diagnostics", "outcome"]) ||
|
|
998
|
+
view.schema !== VIEW_CONFIG.result.schema || view.run !== owner.identity.run || view.run_instance_id !== owner.identity.run_instance_id ||
|
|
999
|
+
view.content_scope !== "canonical_outcome_metadata" || view.primary_text !== "not_included" || !contextCoverage(view.coverage, true) ||
|
|
1000
|
+
Number(captured.stamp.size) !== entry.bytes_total || typeof view.captured_at !== "string" || !Number.isFinite(Date.parse(view.captured_at)) ||
|
|
1001
|
+
new Date(view.captured_at).toISOString() !== view.captured_at)
|
|
1002
|
+
return diagnosticsReadUnavailable("view_invalid");
|
|
1003
|
+
const outcome = diagnosticOutcome(view.outcome);
|
|
1004
|
+
if (!outcome)
|
|
1005
|
+
return diagnosticsReadUnavailable("view_invalid");
|
|
1006
|
+
if (!Object.hasOwn(view, "diagnostics"))
|
|
1007
|
+
return diagnosticsReadUnavailable("publication_unavailable");
|
|
1008
|
+
const descriptor = record(view.diagnostics);
|
|
1009
|
+
if (!descriptor)
|
|
1010
|
+
return diagnosticsReadUnavailable("view_invalid");
|
|
1011
|
+
if (descriptor.state === "not_recorded")
|
|
1012
|
+
return contextFields(descriptor, ["state"]) &&
|
|
1013
|
+
RESULT_DIAGNOSTIC_FIELDS.every(key => !Object.hasOwn(outcome, key)) ? diagnosticsReadUnavailable("diagnostics_unavailable") : diagnosticsReadUnavailable("view_invalid");
|
|
1014
|
+
const source = record(descriptor.source);
|
|
1015
|
+
const previews = record(descriptor.fields);
|
|
1016
|
+
if (!contextFields(descriptor, ["state", "source", "bytes_total", "fields"]) || descriptor.state !== "available" || !source ||
|
|
1017
|
+
!contextFields(source, ["path", "run_instance_id", "sha256"]) || source.path !== resultDiagnosticsPath(root, owner.identity) ||
|
|
1018
|
+
source.run_instance_id !== owner.identity.run_instance_id || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(source.sha256) ||
|
|
1019
|
+
typeof descriptor.bytes_total !== "number" || !Number.isSafeInteger(descriptor.bytes_total) || descriptor.bytes_total <= 0 || descriptor.bytes_total > RUN_RESULT_DIAGNOSTICS_MAX_BYTES ||
|
|
1020
|
+
!previews || !Object.keys(previews).length || !contextFields(previews, RESULT_DIAGNOSTIC_FIELDS))
|
|
1021
|
+
return diagnosticsReadUnavailable("view_invalid");
|
|
1022
|
+
reason = "source_unavailable";
|
|
1023
|
+
const retained = readFixedSourceText(source.path, RUN_RESULT_DIAGNOSTICS_MAX_BYTES, "Run result diagnostics source");
|
|
1024
|
+
reason = "source_invalid";
|
|
1025
|
+
if (Number(retained.stamp.size) !== descriptor.bytes_total || createHash("sha256").update(retained.text).digest("hex") !== source.sha256)
|
|
1026
|
+
return diagnosticsReadUnavailable("source_invalid");
|
|
1027
|
+
const fields = diagnosticSourceFields(retained.text, owner.identity);
|
|
1028
|
+
if (!fields)
|
|
1029
|
+
return diagnosticsReadUnavailable("source_invalid");
|
|
1030
|
+
const excerpts = new Map();
|
|
1031
|
+
for (const key of RESULT_DIAGNOSTIC_FIELDS) {
|
|
1032
|
+
if (!Object.hasOwn(fields, key)) {
|
|
1033
|
+
if (Object.hasOwn(previews, key) || Object.hasOwn(outcome, key))
|
|
1034
|
+
return diagnosticsReadUnavailable("source_invalid");
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
const text = fields[key];
|
|
1038
|
+
if (typeof text !== "string" || Buffer.from(text, "utf8").toString("utf8") !== text)
|
|
1039
|
+
return diagnosticsReadUnavailable("source_invalid");
|
|
1040
|
+
const excerpt = diagnosticExcerpt(text);
|
|
1041
|
+
const { text: preview, ...counts } = excerpt;
|
|
1042
|
+
const recorded = record(previews[key]);
|
|
1043
|
+
const recordedRange = record(recorded?.shown_bytes);
|
|
1044
|
+
const { shown_bytes: bytes, ...scalarCounts } = counts;
|
|
1045
|
+
if (!recorded || !contextFields(recorded, Object.keys(counts)) || !recordedRange || !contextFields(recordedRange, ["start", "end"]) ||
|
|
1046
|
+
recordedRange.start !== bytes.start || recordedRange.end !== bytes.end || Object.entries(scalarCounts).some(([name, value]) => recorded[name] !== value) ||
|
|
1047
|
+
outcome[key] !== preview)
|
|
1048
|
+
return diagnosticsReadUnavailable("source_invalid");
|
|
1049
|
+
excerpts.set(key, excerpt);
|
|
1050
|
+
}
|
|
1051
|
+
const unchanged = () => { try {
|
|
1052
|
+
return frameUnchanged() && sameFile(retained.stamp, lstatSync(source.path, { bigint: true }));
|
|
1053
|
+
}
|
|
1054
|
+
catch {
|
|
1055
|
+
return false;
|
|
1056
|
+
} };
|
|
1057
|
+
if (!unchanged())
|
|
1058
|
+
return diagnosticsReadUnavailable("observation_changed");
|
|
1059
|
+
let output = excerpts.get(field);
|
|
1060
|
+
if (!output)
|
|
1061
|
+
return diagnosticsReadUnavailable("field_unavailable");
|
|
1062
|
+
if (range) {
|
|
1063
|
+
const bytes = Buffer.from(fields[field], "utf8");
|
|
1064
|
+
if (range.end > bytes.length || !primaryBoundary(bytes, range.start) || !primaryBoundary(bytes, range.end))
|
|
1065
|
+
return diagnosticsReadUnavailable("range_invalid");
|
|
1066
|
+
output = diagnosticExcerpt(fields[field], range);
|
|
1067
|
+
}
|
|
1068
|
+
const reading = Object.freeze({ state: "available", identity: owner.identity,
|
|
1069
|
+
recorded_acceptance: outcome.acceptance, field, content_scope: "canonical_outcome_diagnostic_strings", privacy_scope: "producer_admission",
|
|
1070
|
+
primary_text: "not_included", redacted: true, selection: range ? "byte_range" : "recorded_preview",
|
|
1071
|
+
diagnostics: Object.freeze({ source: Object.freeze({ path: source.path, run_instance_id: source.run_instance_id, sha256: source.sha256 }), bytes_total: descriptor.bytes_total }),
|
|
1072
|
+
output, unchanged });
|
|
1073
|
+
if (Buffer.byteLength(JSON.stringify(reading)) > RUN_CONTEXT_RESULT_MAX_BYTES)
|
|
1074
|
+
return diagnosticsReadUnavailable("range_too_large");
|
|
1075
|
+
return unchanged() ? reading : diagnosticsReadUnavailable("observation_changed");
|
|
1076
|
+
}
|
|
1077
|
+
catch {
|
|
1078
|
+
return diagnosticsReadUnavailable(reason);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
// Cwd anchoring can amplify bounded input metadata; refuse before constructing/projecting that expansion.
|
|
1082
|
+
function artifactReferenceBudget(artifacts, cwd) {
|
|
1083
|
+
const declarations = record(artifacts);
|
|
1084
|
+
if (!declarations)
|
|
1085
|
+
return true; // Validation still belongs to the reference owner.
|
|
1086
|
+
const baseBytes = typeof cwd === "string" ? Buffer.byteLength(cwd) + (cwd.endsWith(sep) ? 0 : Buffer.byteLength(sep)) : 0;
|
|
1087
|
+
let bytes = 0;
|
|
1088
|
+
for (const value of Object.values(declarations)) {
|
|
1089
|
+
const path = typeof value === "string" ? value : record(value)?.path;
|
|
1090
|
+
if (typeof path !== "string")
|
|
1091
|
+
continue;
|
|
1092
|
+
bytes += Buffer.byteLength(path) + (isAbsolute(path) ? 0 : baseBytes);
|
|
1093
|
+
if (bytes > RUN_ARTIFACT_INVENTORY_MAX_BYTES)
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
function captureArtifacts(stateDir, producer) {
|
|
1099
|
+
const metadata = readContextMetadata(stateDir, producer);
|
|
1100
|
+
if (metadata.state === "unavailable")
|
|
1101
|
+
return artifactUnavailable(metadata.reason === "changed" ? "artifact_metadata_changed" : "artifact_metadata_unavailable");
|
|
1102
|
+
if (!artifactReferenceBudget(metadata.meta.artifacts, metadata.meta.cwd))
|
|
1103
|
+
return artifactUnavailable("artifact_manifest_too_large");
|
|
1104
|
+
const described = describeRunArtifactReferences(metadata.meta.artifacts, metadata.meta.cwd);
|
|
1105
|
+
if (described.state === "unavailable")
|
|
1106
|
+
return artifactUnavailable(described.reason);
|
|
1107
|
+
if (!described.references.length)
|
|
1108
|
+
return artifactUnavailable("artifact_declarations_absent");
|
|
1109
|
+
const original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
1110
|
+
if (!original)
|
|
1111
|
+
return artifactUnavailable("artifact_admission_unavailable");
|
|
1112
|
+
if (!createContextIdentityPrivacyCheck(original)({ run: producer.run, run_instance_id: producer.runInstanceId,
|
|
1113
|
+
...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) }))
|
|
1114
|
+
return artifactUnavailable("artifact_identity_redacted");
|
|
1115
|
+
const redact = (value) => redactSessionEvidenceValue(value, Number.MAX_SAFE_INTEGER);
|
|
1116
|
+
// Declared output paths are operational references: never turn a redacted path into a promised readable file.
|
|
1117
|
+
const prepared = described.references.map(reference => ({ name: redact(reference.name),
|
|
1118
|
+
path: redact(reference.path) === reference.path ? reference.path : null,
|
|
1119
|
+
...(reference.kind !== undefined ? { kind: redact(reference.kind) } : {}),
|
|
1120
|
+
...(reference.media_type !== undefined ? { media_type: redact(reference.media_type) } : {}),
|
|
1121
|
+
...(reference.required !== undefined ? { required: reference.required } : {}) }));
|
|
1122
|
+
if (Buffer.byteLength(JSON.stringify(prepared), "utf8") > RUN_CONTEXT_ARTIFACT_MAX_BYTES)
|
|
1123
|
+
return artifactUnavailable("artifact_manifest_too_large");
|
|
1124
|
+
const artifacts = prepared.map((entry, index) => {
|
|
1125
|
+
if (entry.path === null)
|
|
1126
|
+
return { ...entry, availability: "unavailable", reason: "artifact_reference_redacted", mutable: true };
|
|
1127
|
+
const observed = observeRunArtifactReference(described.references[index]);
|
|
1128
|
+
return { ...entry, availability: observed.availability, mutable: true,
|
|
1129
|
+
...(observed.bytes_total !== undefined ? { bytes_total: observed.bytes_total } : {}),
|
|
1130
|
+
...(observed.reason ? { reason: observed.reason } : {}) };
|
|
1131
|
+
});
|
|
1132
|
+
const after = optionalStamp(metadata.path);
|
|
1133
|
+
if (!after || !sameFile(metadata.stamp, after))
|
|
1134
|
+
return artifactUnavailable("artifact_metadata_changed");
|
|
1135
|
+
const summary = { declared: artifacts.length, available: 0, missing: 0, unavailable: 0 };
|
|
1136
|
+
for (const artifact of artifacts)
|
|
1137
|
+
summary[artifact.availability] += 1;
|
|
1138
|
+
const capturedAt = new Date().toISOString();
|
|
1139
|
+
const content = JSON.stringify({ schema: VIEW_CONFIG.artifacts.schema, run: producer.run, run_instance_id: producer.runInstanceId,
|
|
1140
|
+
captured_at: capturedAt, content_scope: "captured_artifact_references", contents: "not_read", production_provenance: "not_asserted",
|
|
1141
|
+
coverage: ARTIFACT_VIEW_COVERAGE, summary, artifacts }, null, 2) + "\n";
|
|
1142
|
+
if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_ARTIFACT_MAX_BYTES)
|
|
1143
|
+
return artifactUnavailable("artifact_manifest_too_large");
|
|
1144
|
+
return Object.freeze({ status: "captured", captured_at: capturedAt, content });
|
|
1145
|
+
}
|
|
1146
|
+
function artifactInventoryPath(root, identity) {
|
|
1147
|
+
const key = createHash("sha256").update(JSON.stringify([identity.run, identity.run_instance_id, identity.ownerId ?? null])).digest("hex");
|
|
1148
|
+
return join(root, "evidence", `artifact-inventory-${key}.json`);
|
|
1149
|
+
}
|
|
1150
|
+
/** Overflow source permission is fixed at genuine producer admission, not refreshed from live declarations. */
|
|
1151
|
+
function retainArtifactInventoryView(stateDir, producer) {
|
|
1152
|
+
return withRunProducerEvidence(stateDir, producer, () => {
|
|
1153
|
+
const captured = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
1154
|
+
if (!captured || !createContextIdentityPrivacyCheck(captured)({ run: producer.run, run_instance_id: producer.runInstanceId,
|
|
1155
|
+
...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) }))
|
|
1156
|
+
return undefined;
|
|
1157
|
+
const metadata = readContextMetadata(stateDir, producer);
|
|
1158
|
+
if (metadata.state !== "available" ||
|
|
1159
|
+
JSON.stringify([captured.artifacts, captured.cwd]) !== JSON.stringify([metadata.meta.artifacts, metadata.meta.cwd]))
|
|
1160
|
+
return undefined;
|
|
1161
|
+
if (!artifactReferenceBudget(captured.artifacts, captured.cwd))
|
|
1162
|
+
return undefined;
|
|
1163
|
+
const described = describeRunArtifactReferences(captured.artifacts, captured.cwd);
|
|
1164
|
+
if (described.state !== "available" || !described.references.length)
|
|
1165
|
+
return undefined;
|
|
1166
|
+
const mask = capturedSecretMask(captured), withoutSecrets = capturedSecretMask(captured, "");
|
|
1167
|
+
// Operational output references may be beneath Recipe origins; share secret masking, not origin stripping.
|
|
1168
|
+
const redact = (text) => mask(redactSessionEvidenceValue(text, Number.MAX_SAFE_INTEGER));
|
|
1169
|
+
const artifacts = described.references.map(reference => {
|
|
1170
|
+
const withheld = withoutSecrets(reference.path) !== reference.path || redact(reference.path) !== reference.path;
|
|
1171
|
+
return { name: redact(reference.name), path: withheld ? null : reference.path,
|
|
1172
|
+
...(reference.kind !== undefined ? { kind: redact(reference.kind) } : {}),
|
|
1173
|
+
...(reference.media_type !== undefined ? { media_type: redact(reference.media_type) } : {}),
|
|
1174
|
+
...(reference.required !== undefined ? { required: reference.required } : {}),
|
|
1175
|
+
availability: "not_observed", ...(withheld ? { reason: "artifact_reference_redacted" } : {}) };
|
|
1176
|
+
});
|
|
1177
|
+
const coverage = Object.freeze({ truncated: false, redacted: true });
|
|
1178
|
+
const summary = { declared: artifacts.length, references_disclosed: artifacts.filter(item => item.path !== null).length,
|
|
1179
|
+
references_withheld: artifacts.filter(item => item.path === null).length };
|
|
1180
|
+
const common = { run: producer.run, run_instance_id: producer.runInstanceId,
|
|
1181
|
+
capture_scope: "producer_admission", contents: "not_read", availability: "not_observed", production_provenance: "not_asserted" };
|
|
1182
|
+
const content = JSON.stringify({ schema: "run-artifact-inventory-v1", ...common,
|
|
1183
|
+
content_scope: "captured_artifact_declarations", inventory_complete: true, coverage, summary, artifacts }, null, 2) + "\n";
|
|
1184
|
+
const bytes = Buffer.byteLength(content);
|
|
1185
|
+
if (bytes > RUN_ARTIFACT_INVENTORY_MAX_BYTES)
|
|
1186
|
+
return undefined;
|
|
1187
|
+
const path = artifactInventoryPath(metadata.root, { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) });
|
|
1188
|
+
const inventory = { source: { path, run_instance_id: producer.runInstanceId, sha256: createHash("sha256").update(content).digest("hex") },
|
|
1189
|
+
bytes_total: bytes, declarations_total: artifacts.length };
|
|
1190
|
+
const capturedAt = new Date().toISOString();
|
|
1191
|
+
const viewCoverage = Object.freeze({ truncated: true, redacted: true });
|
|
1192
|
+
const view = JSON.stringify({ schema: VIEW_CONFIG.artifacts.schema, ...common, captured_at: capturedAt,
|
|
1193
|
+
content_scope: "captured_artifact_inventory_reference", coverage: viewCoverage, summary,
|
|
1194
|
+
artifacts: [], artifacts_shown: 0, artifacts_omitted: artifacts.length, inventory }, null, 2) + "\n";
|
|
1195
|
+
if (Buffer.byteLength(view) > RUN_CONTEXT_ARTIFACT_MAX_BYTES)
|
|
1196
|
+
return undefined;
|
|
1197
|
+
const retained = retainContextSource(metadata, path, content, RUN_ARTIFACT_INVENTORY_MAX_BYTES, "Run artifact inventory");
|
|
1198
|
+
return Object.freeze({ status: "captured", captured_at: capturedAt, content: view, coverage: viewCoverage, unchanged: retained.unchanged });
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
/** Kernel preparation, before work/result publication. This projects declarations only, never artifact targets. */
|
|
1202
|
+
export function prepareRunArtifactInventoryPublication(stateDir, producer) {
|
|
1203
|
+
const metadata = withRunProducerEvidence(stateDir, producer, () => readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES));
|
|
1204
|
+
let binding = Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "not_declared" });
|
|
1205
|
+
let unchanged;
|
|
1206
|
+
let failure;
|
|
1207
|
+
if (!metadata)
|
|
1208
|
+
binding = Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "privacy_capture_unavailable" });
|
|
1209
|
+
else if (metadata.artifacts !== undefined && (!record(metadata.artifacts) || Object.keys(metadata.artifacts).length > 0)) {
|
|
1210
|
+
if (!createContextIdentityPrivacyCheck(metadata)({ run: producer.run, run_instance_id: producer.runInstanceId,
|
|
1211
|
+
...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) }))
|
|
1212
|
+
return Object.freeze({
|
|
1213
|
+
snapshot: () => Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "identity_redacted" }), readFailure: () => undefined
|
|
1214
|
+
});
|
|
1215
|
+
try {
|
|
1216
|
+
const captured = retainArtifactInventoryView(stateDir, producer);
|
|
1217
|
+
if (!captured)
|
|
1218
|
+
binding = Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "capture_unavailable" });
|
|
1219
|
+
else {
|
|
1220
|
+
const view = publishContextView(stateDir, producer, "artifacts", captured);
|
|
1221
|
+
publishContextIndex(stateDir, producer, "artifacts", view);
|
|
1222
|
+
const reading = readRunArtifactInventorySource(stateDir, { run: producer.run, run_instance_id: producer.runInstanceId,
|
|
1223
|
+
...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) }, { start: 0, end: 0 });
|
|
1224
|
+
binding = decodeRunArtifactInventoryBinding(stateDir, producer.runInstanceId, reading.state === "available"
|
|
1225
|
+
? { artifact_inventory: reading.inventory } : { artifact_inventory: null, artifact_inventory_unavailable: reading.reason });
|
|
1226
|
+
if (reading.state === "available")
|
|
1227
|
+
unchanged = reading.unchanged;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
catch (error) {
|
|
1231
|
+
if (error instanceof ContextExportBindingRefusal || error instanceof PrimaryTextBindingRefusal)
|
|
1232
|
+
throw error;
|
|
1233
|
+
// Ordinary evidence I/O may be unavailable, but loss of genuine producer authority must still fail closed.
|
|
1234
|
+
try {
|
|
1235
|
+
withRunProducerEvidence(stateDir, producer, () => { });
|
|
1236
|
+
}
|
|
1237
|
+
catch (authorityError) {
|
|
1238
|
+
throw new AggregateError([error, authorityError], "Run artifact inventory authority changed after publication failure.");
|
|
1239
|
+
}
|
|
1240
|
+
failure = error;
|
|
1241
|
+
binding = Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "publication_failed" });
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
return Object.freeze({ snapshot: () => binding.artifact_inventory && !unchanged?.()
|
|
1245
|
+
? Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "observation_changed" }) : binding,
|
|
1246
|
+
readFailure: () => failure });
|
|
1247
|
+
}
|
|
1248
|
+
/** Exact natural first-result binding plus retained-source admission; call only for eligible packet members, never ambient checks. */
|
|
1249
|
+
export function readRunResultArtifactInventory(stateDir, expected) {
|
|
1250
|
+
const unavailable = (reason, saved) => Object.freeze({ state: "unavailable", reason,
|
|
1251
|
+
...(saved === undefined ? {} : { artifact_inventory_unavailable: saved }) });
|
|
1252
|
+
const request = contextReadIdentity(expected);
|
|
1253
|
+
if (!request)
|
|
1254
|
+
return unavailable("selection_invalid");
|
|
1255
|
+
const owner = readRunContextIdentity(stateDir, request);
|
|
1256
|
+
if (owner.state !== "available")
|
|
1257
|
+
return unavailable(owner.reason);
|
|
1258
|
+
let reason = "result_unavailable";
|
|
1259
|
+
try {
|
|
1260
|
+
const root = owner.local_path;
|
|
1261
|
+
const metadata = contextJson(join(root, "run.json"));
|
|
1262
|
+
const meta = metadata.value;
|
|
1263
|
+
if (meta.outcome_protocol !== RUN_OUTCOME_PROTOCOL || !Number.isSafeInteger(meta.pid) || Number(meta.pid) <= 0)
|
|
1264
|
+
return unavailable("publication_unavailable");
|
|
1265
|
+
const resultPath = join(root, "result.json");
|
|
1266
|
+
const canonical = contextJson(resultPath);
|
|
1267
|
+
const snapshot = readRunNaturalOutcomeSnapshot(root, meta, () => canonical.value);
|
|
1268
|
+
const frameUnchanged = () => {
|
|
1269
|
+
try {
|
|
1270
|
+
return owner.unchanged() && sameFile(metadata.stamp, lstatSync(join(root, "run.json"), { bigint: true })) && sameFile(canonical.stamp, lstatSync(resultPath, { bigint: true }));
|
|
1271
|
+
}
|
|
1272
|
+
catch {
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
if (!frameUnchanged())
|
|
1277
|
+
return unavailable("observation_changed");
|
|
1278
|
+
if (snapshot.outcome.state === "captured")
|
|
1279
|
+
return unavailable("nonterminal_capture");
|
|
1280
|
+
if (!snapshot.result || snapshot.outcome.state !== "accepted")
|
|
1281
|
+
return unavailable("result_unavailable");
|
|
1282
|
+
if (snapshot.result.artifact_inventory_protocol !== RUN_ARTIFACT_INVENTORY_PROTOCOL)
|
|
1283
|
+
return unavailable("publication_unavailable");
|
|
1284
|
+
reason = "binding_invalid";
|
|
1285
|
+
const binding = decodeRunArtifactInventoryBinding(root, owner.identity.run_instance_id, snapshot.result);
|
|
1286
|
+
if (!binding.artifact_inventory)
|
|
1287
|
+
return frameUnchanged() ? unavailable("inventory_unavailable", binding.artifact_inventory_unavailable) : unavailable("observation_changed");
|
|
1288
|
+
if (binding.artifact_inventory.source.path !== artifactInventoryPath(root, owner.identity))
|
|
1289
|
+
return unavailable("binding_invalid");
|
|
1290
|
+
reason = "source_unavailable";
|
|
1291
|
+
const reading = readRunArtifactInventorySource(root, owner.identity, { start: 0, end: 0 });
|
|
1292
|
+
if (reading.state !== "available")
|
|
1293
|
+
return unavailable(reading.reason);
|
|
1294
|
+
const expectedSource = binding.artifact_inventory;
|
|
1295
|
+
const actual = reading.inventory;
|
|
1296
|
+
if (expectedSource.source.path !== actual.source.path || expectedSource.source.run_instance_id !== actual.source.run_instance_id ||
|
|
1297
|
+
expectedSource.source.sha256 !== actual.source.sha256 || expectedSource.bytes_total !== actual.bytes_total || expectedSource.declarations_total !== actual.declarations_total)
|
|
1298
|
+
return unavailable("binding_invalid");
|
|
1299
|
+
const unchanged = () => frameUnchanged() && reading.unchanged();
|
|
1300
|
+
return unchanged() ? Object.freeze({ state: "available", identity: owner.identity, binding, terminal_status: snapshot.outcome.status, unchanged }) : unavailable("observation_changed");
|
|
1301
|
+
}
|
|
1302
|
+
catch {
|
|
1303
|
+
return unavailable(reason);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
function artifactReadUnavailable(reason) {
|
|
1307
|
+
return Object.freeze({ state: "unavailable", reason });
|
|
1308
|
+
}
|
|
1309
|
+
function contextFields(value, fields) {
|
|
1310
|
+
return Object.keys(value).every(key => fields.includes(key));
|
|
1311
|
+
}
|
|
1312
|
+
function artifactSummary(value, total) {
|
|
1313
|
+
const source = record(value);
|
|
1314
|
+
if (!source || !contextFields(source, ["declared", "references_disclosed", "references_withheld"]) || source.declared !== total ||
|
|
1315
|
+
typeof source.references_disclosed !== "number" || !Number.isSafeInteger(source.references_disclosed) || source.references_disclosed < 0 ||
|
|
1316
|
+
typeof source.references_withheld !== "number" || !Number.isSafeInteger(source.references_withheld) || source.references_withheld < 0 ||
|
|
1317
|
+
source.references_disclosed + source.references_withheld !== total)
|
|
1318
|
+
return undefined;
|
|
1319
|
+
return Object.freeze({ declared: total, references_disclosed: source.references_disclosed, references_withheld: source.references_withheld });
|
|
1320
|
+
}
|
|
1321
|
+
function artifactDeclaration(value) {
|
|
1322
|
+
const source = record(value);
|
|
1323
|
+
if (!source || !contextFields(source, ["name", "path", "kind", "media_type", "required", "availability", "reason"]) ||
|
|
1324
|
+
typeof source.name !== "string" || !source.name.trim() || /[\u0000-\u001f\u007f]/u.test(source.name) || source.availability !== "not_observed" ||
|
|
1325
|
+
(source.kind !== undefined && typeof source.kind !== "string") || (source.media_type !== undefined && typeof source.media_type !== "string") ||
|
|
1326
|
+
(source.required !== undefined && typeof source.required !== "boolean"))
|
|
1327
|
+
return undefined;
|
|
1328
|
+
if (source.path === null ? source.reason !== "artifact_reference_redacted" : typeof source.path !== "string" || !isAbsolute(source.path) ||
|
|
1329
|
+
/[\u0000-\u001f\u007f]/u.test(source.path) || source.reason !== undefined)
|
|
1330
|
+
return undefined;
|
|
1331
|
+
return Object.freeze({ name: source.name, path: source.path, availability: "not_observed",
|
|
1332
|
+
...(source.kind === undefined ? {} : { kind: source.kind }), ...(source.media_type === undefined ? {} : { media_type: source.media_type }),
|
|
1333
|
+
...(source.required === undefined ? {} : { required: source.required }), ...(source.path === null ? { reason: "artifact_reference_redacted" } : {}) });
|
|
1334
|
+
}
|
|
1335
|
+
function artifactHeader(value, identity) {
|
|
1336
|
+
return value.run === identity.run && value.run_instance_id === identity.run_instance_id && value.capture_scope === "producer_admission" &&
|
|
1337
|
+
value.contents === "not_read" && value.availability === "not_observed" && value.production_provenance === "not_asserted";
|
|
1338
|
+
}
|
|
1339
|
+
function contextCoverage(value, truncated) {
|
|
1340
|
+
const coverage = record(value);
|
|
1341
|
+
return !!coverage && contextFields(coverage, ["truncated", "redacted"]) && coverage.truncated === truncated && coverage.redacted === true;
|
|
1342
|
+
}
|
|
1343
|
+
/** Fixed retained-projection policy, not permission inferred from a path/flag. Query is a half-open declaration range, not bytes. */
|
|
1344
|
+
export function readRunArtifactInventorySource(stateDir, expected, query) {
|
|
1345
|
+
const request = contextReadIdentity(expected);
|
|
1346
|
+
if (!request)
|
|
1347
|
+
return artifactReadUnavailable("selection_invalid");
|
|
1348
|
+
let range;
|
|
1349
|
+
if (query !== undefined) {
|
|
1350
|
+
const value = contextReadRequest(query);
|
|
1351
|
+
if (!value || !contextFields(value, ["start", "end"]) || !Number.isSafeInteger(value.start) || !Number.isSafeInteger(value.end) ||
|
|
1352
|
+
Number(value.start) < 0 || Number(value.end) < Number(value.start))
|
|
1353
|
+
return artifactReadUnavailable("selection_invalid");
|
|
1354
|
+
range = { start: Number(value.start), end: Number(value.end) };
|
|
1355
|
+
if (range.end - range.start > RUN_CONTEXT_ARTIFACT_MAX_BYTES)
|
|
1356
|
+
return artifactReadUnavailable("range_too_large");
|
|
1357
|
+
}
|
|
1358
|
+
const owner = readRunContextIdentity(stateDir, request);
|
|
1359
|
+
if (owner.state !== "available")
|
|
1360
|
+
return artifactReadUnavailable(owner.reason);
|
|
1361
|
+
let reason = "index_unavailable";
|
|
1362
|
+
try {
|
|
1363
|
+
const root = owner.local_path;
|
|
1364
|
+
const indexPath = join(root, RUN_CONTEXT_INDEX_FILE);
|
|
1365
|
+
const index = optionalStamp(indexPath);
|
|
1366
|
+
if (!index?.isFile())
|
|
1367
|
+
return artifactReadUnavailable("index_unavailable");
|
|
1368
|
+
const admitted = readRunContextIndex(root, owner.identity);
|
|
1369
|
+
if (admitted.state !== "available")
|
|
1370
|
+
return artifactReadUnavailable(admitted.reason === "observation_changed" ? "observation_changed" : "index_unavailable");
|
|
1371
|
+
const entry = admitted.index.entries.artifacts;
|
|
1372
|
+
if (entry.availability !== "available" || entry.mutable !== true || !contextCoverage(entry.coverage, true))
|
|
1373
|
+
return artifactReadUnavailable("publication_unavailable");
|
|
1374
|
+
const evidencePath = join(root, "evidence");
|
|
1375
|
+
const evidence = lstatSync(evidencePath, { bigint: true });
|
|
1376
|
+
if (!evidence.isDirectory() || evidence.isSymbolicLink())
|
|
1377
|
+
return artifactReadUnavailable("view_unavailable");
|
|
1378
|
+
reason = "view_unavailable";
|
|
1379
|
+
const viewPath = join(root, runContextExportPath("artifacts"));
|
|
1380
|
+
const captured = readFixedSourceText(viewPath, RUN_CONTEXT_ARTIFACT_MAX_BYTES, "Run artifact inventory view");
|
|
1381
|
+
const frameUnchanged = () => {
|
|
1382
|
+
try {
|
|
1383
|
+
return owner.unchanged() && sameFile(index, lstatSync(indexPath, { bigint: true })) &&
|
|
1384
|
+
sameObject(evidence, lstatSync(evidencePath, { bigint: true })) && sameFile(captured.stamp, lstatSync(viewPath, { bigint: true }));
|
|
1385
|
+
}
|
|
1386
|
+
catch {
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
if (!frameUnchanged())
|
|
1391
|
+
return artifactReadUnavailable("observation_changed");
|
|
1392
|
+
reason = "view_invalid";
|
|
1393
|
+
const view = record(JSON.parse(captured.text));
|
|
1394
|
+
if (!view || Number(captured.stamp.size) !== entry.bytes_total || !contextFields(view, ["schema", "run", "run_instance_id", "capture_scope", "contents", "availability", "production_provenance",
|
|
1395
|
+
"captured_at", "content_scope", "coverage", "summary", "artifacts", "artifacts_shown", "artifacts_omitted", "inventory"]) ||
|
|
1396
|
+
view.schema !== VIEW_CONFIG.artifacts.schema || !artifactHeader(view, owner.identity) || view.content_scope !== "captured_artifact_inventory_reference" ||
|
|
1397
|
+
!contextCoverage(view.coverage, true) || !Array.isArray(view.artifacts) || view.artifacts.length !== 0 || view.artifacts_shown !== 0 ||
|
|
1398
|
+
typeof view.captured_at !== "string" || !Number.isFinite(Date.parse(view.captured_at)) || new Date(view.captured_at).toISOString() !== view.captured_at)
|
|
1399
|
+
return artifactReadUnavailable("view_invalid");
|
|
1400
|
+
const descriptor = record(view.inventory);
|
|
1401
|
+
const source = record(descriptor?.source);
|
|
1402
|
+
const total = descriptor?.declarations_total;
|
|
1403
|
+
if (!descriptor || !contextFields(descriptor, ["source", "bytes_total", "declarations_total"]) || !source || !contextFields(source, ["path", "run_instance_id", "sha256"]) ||
|
|
1404
|
+
source.path !== artifactInventoryPath(root, owner.identity) || source.run_instance_id !== owner.identity.run_instance_id ||
|
|
1405
|
+
typeof source.sha256 !== "string" || source.sha256.length !== 64 || !/^[a-f0-9]{64}$/u.test(source.sha256) ||
|
|
1406
|
+
typeof descriptor.bytes_total !== "number" || !Number.isSafeInteger(descriptor.bytes_total) || descriptor.bytes_total <= 0 || descriptor.bytes_total > RUN_ARTIFACT_INVENTORY_MAX_BYTES ||
|
|
1407
|
+
typeof total !== "number" || !Number.isSafeInteger(total) || total <= 0 || view.artifacts_omitted !== total)
|
|
1408
|
+
return artifactReadUnavailable("view_invalid");
|
|
1409
|
+
const summary = artifactSummary(view.summary, total);
|
|
1410
|
+
if (!summary)
|
|
1411
|
+
return artifactReadUnavailable("view_invalid");
|
|
1412
|
+
const selection = range ?? { start: 0, end: Math.min(total, DEFAULT_INSPECT_LINES) };
|
|
1413
|
+
if (selection.end > total)
|
|
1414
|
+
return artifactReadUnavailable("range_invalid");
|
|
1415
|
+
reason = "source_unavailable";
|
|
1416
|
+
const retained = readFixedSourceText(source.path, RUN_ARTIFACT_INVENTORY_MAX_BYTES, "Run artifact inventory source");
|
|
1417
|
+
reason = "source_invalid";
|
|
1418
|
+
if (Number(retained.stamp.size) !== descriptor.bytes_total || createHash("sha256").update(retained.text).digest("hex") !== source.sha256)
|
|
1419
|
+
return artifactReadUnavailable("source_invalid");
|
|
1420
|
+
const inventory = record(JSON.parse(retained.text));
|
|
1421
|
+
if (!inventory || !contextFields(inventory, ["schema", "run", "run_instance_id", "capture_scope", "contents", "availability", "production_provenance", "content_scope", "inventory_complete", "coverage", "summary", "artifacts"]) ||
|
|
1422
|
+
inventory.schema !== "run-artifact-inventory-v1" || !artifactHeader(inventory, owner.identity) || inventory.content_scope !== "captured_artifact_declarations" ||
|
|
1423
|
+
inventory.inventory_complete !== true || !contextCoverage(inventory.coverage, false) || !Array.isArray(inventory.artifacts) || inventory.artifacts.length !== total)
|
|
1424
|
+
return artifactReadUnavailable("source_invalid");
|
|
1425
|
+
const sourceSummary = artifactSummary(inventory.summary, total);
|
|
1426
|
+
if (!sourceSummary || sourceSummary.references_disclosed !== summary.references_disclosed || sourceSummary.references_withheld !== summary.references_withheld)
|
|
1427
|
+
return artifactReadUnavailable("source_invalid");
|
|
1428
|
+
const declarations = [];
|
|
1429
|
+
let withheld = 0;
|
|
1430
|
+
for (const raw of inventory.artifacts) {
|
|
1431
|
+
const item = artifactDeclaration(raw);
|
|
1432
|
+
if (!item)
|
|
1433
|
+
return artifactReadUnavailable("source_invalid");
|
|
1434
|
+
declarations.push(item);
|
|
1435
|
+
if (item.path === null)
|
|
1436
|
+
withheld++;
|
|
1437
|
+
}
|
|
1438
|
+
if (withheld !== summary.references_withheld)
|
|
1439
|
+
return artifactReadUnavailable("source_invalid");
|
|
1440
|
+
const unchanged = () => { try {
|
|
1441
|
+
return frameUnchanged() && sameFile(retained.stamp, lstatSync(source.path, { bigint: true }));
|
|
1442
|
+
}
|
|
1443
|
+
catch {
|
|
1444
|
+
return false;
|
|
1445
|
+
} };
|
|
1446
|
+
if (!unchanged())
|
|
1447
|
+
return artifactReadUnavailable("observation_changed");
|
|
1448
|
+
const result = Object.freeze({ state: "available", identity: owner.identity,
|
|
1449
|
+
inventory: Object.freeze({ source: Object.freeze({ path: source.path, run_instance_id: source.run_instance_id, sha256: source.sha256 }), bytes_total: descriptor.bytes_total, declarations_total: total }),
|
|
1450
|
+
capture_scope: "producer_admission", contents: "not_read", availability: "not_observed", production_provenance: "not_asserted", summary,
|
|
1451
|
+
selection: Object.freeze(selection), artifacts: Object.freeze(declarations.slice(selection.start, selection.end)), artifacts_omitted: total - (selection.end - selection.start),
|
|
1452
|
+
coverage: Object.freeze({ truncated: selection.start > 0 || selection.end < total, redacted: true }), unchanged });
|
|
1453
|
+
if (Buffer.byteLength(JSON.stringify(result)) > RUN_CONTEXT_ARTIFACT_MAX_BYTES)
|
|
1454
|
+
return artifactReadUnavailable("range_too_large");
|
|
1455
|
+
return unchanged() ? result : artifactReadUnavailable("observation_changed");
|
|
1456
|
+
}
|
|
1457
|
+
catch {
|
|
1458
|
+
return artifactReadUnavailable(reason);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
function captureExecution(stateDir, producer) {
|
|
1462
|
+
const metadata = readContextMetadata(stateDir, producer);
|
|
1463
|
+
if (metadata.state === "unavailable")
|
|
1464
|
+
return executionUnavailable(metadata.reason === "changed" ? "execution_changed" : "execution_metadata_unavailable");
|
|
1465
|
+
const path = join(metadata.root, "execution.json");
|
|
1466
|
+
const before = optionalStamp(path);
|
|
1467
|
+
if (!before)
|
|
1468
|
+
return executionUnavailable("execution_absent");
|
|
1469
|
+
if (!before.isFile() || before.size > BigInt(RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES))
|
|
1470
|
+
return executionUnavailable("execution_unavailable");
|
|
1471
|
+
const metadataUnchanged = captureContextMetadataFence(metadata, producer);
|
|
1472
|
+
if (!metadataUnchanged)
|
|
1473
|
+
return executionUnavailable("execution_changed");
|
|
1474
|
+
// Execution facts stay current; their Recipe privacy is fixed before reading the manifest body.
|
|
1475
|
+
const recipe = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
1476
|
+
if (!recipe)
|
|
1477
|
+
return executionUnavailable("execution_admission_unavailable");
|
|
1478
|
+
const metadataUnredacted = createContextIdentityPrivacyCheck(recipe);
|
|
1479
|
+
if (!metadataUnredacted({ run: producer.run, run_instance_id: producer.runInstanceId }))
|
|
1480
|
+
return executionUnavailable("execution_identity_redacted");
|
|
1481
|
+
const read = readJsonFileResilient(path, undefined, { maxBytes: RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES, strictUtf8: true });
|
|
1482
|
+
const after = optionalStamp(path);
|
|
1483
|
+
const metaAfter = optionalStamp(metadata.path);
|
|
1484
|
+
if (!after || !sameFile(before, after) || !metaAfter || !sameFile(metadata.stamp, metaAfter) || !metadataUnchanged())
|
|
1485
|
+
return executionUnavailable("execution_changed");
|
|
1486
|
+
if (read.diagnostics.length)
|
|
1487
|
+
return executionUnavailable("execution_invalid");
|
|
1488
|
+
const projected = projectRunExecutionManifest({ run: producer.run, run_instance_id: producer.runInstanceId }, read.value, recipe, createRunRecipeBodyRedactor(recipe));
|
|
1489
|
+
if (projected.state === "unavailable")
|
|
1490
|
+
return executionUnavailable("execution_invalid");
|
|
1491
|
+
// Source facts and report/reference tokens stay exact; privacy refuses this view, not the actual root outcome.
|
|
1492
|
+
if (!metadataUnredacted({ updated_at: projected.view.updated_at }) || projected.view.commands.some(command => !metadataUnredacted({ id: command.id, ...(command.started_at === undefined ? {} : { started_at: command.started_at }),
|
|
1493
|
+
...(command.completed_at === undefined ? {} : { completed_at: command.completed_at }) }) ||
|
|
1494
|
+
command.attempts.some(attempt => attempt.native_signal !== undefined && !metadataUnredacted({ native_signal: attempt.native_signal })))) {
|
|
1495
|
+
return executionUnavailable("execution_metadata_redacted");
|
|
1496
|
+
}
|
|
1497
|
+
const capturedAt = new Date().toISOString();
|
|
1498
|
+
if (!metadataUnredacted({ captured_at: capturedAt }))
|
|
1499
|
+
return executionUnavailable("execution_metadata_redacted");
|
|
1500
|
+
const content = JSON.stringify({ schema: VIEW_CONFIG.execution.schema, run: producer.run, run_instance_id: producer.runInstanceId,
|
|
1501
|
+
captured_at: capturedAt, coverage: EXECUTION_VIEW_COVERAGE, source_scope: "execution_manifest",
|
|
1502
|
+
capture_references: "not_exported", capture_bodies: "not_read", session_bodies: "not_read", scope_closure: "not_asserted", execution: projected.view }, null, 2) + "\n";
|
|
1503
|
+
if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_EXECUTION_MAX_BYTES)
|
|
1504
|
+
return executionUnavailable("execution_view_too_large");
|
|
1505
|
+
const unchanged = () => {
|
|
1506
|
+
try {
|
|
1507
|
+
return metadataUnchanged() && sameFile(after, lstatSync(path, { bigint: true }));
|
|
1508
|
+
}
|
|
1509
|
+
catch {
|
|
1510
|
+
return false;
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
return unchanged() ? Object.freeze({ status: "captured", captured_at: capturedAt, content, unchanged, original_root: metadata.root }) : executionUnavailable("execution_changed");
|
|
1514
|
+
}
|
|
1515
|
+
/** The original runtime producer is required twice: permission to read/project is not permission to publish after replacement. */
|
|
1516
|
+
export function writeRunControlContextExport(stateDir, producer) {
|
|
1517
|
+
const captured = withRunProducerEvidence(stateDir, producer, context => {
|
|
1518
|
+
try {
|
|
1519
|
+
return captureControl(stateDir, producer, context.status);
|
|
1520
|
+
}
|
|
1521
|
+
catch {
|
|
1522
|
+
return unavailable("control_journal_unavailable");
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
if (captured.status === "unavailable")
|
|
1526
|
+
return captured;
|
|
1527
|
+
const written = publishContextView(stateDir, producer, "control", captured);
|
|
1528
|
+
// Post-success only, including lifecycle release; later index errors remain a separate effect.
|
|
1529
|
+
if (!readRunProducerCleanupFailure(producer))
|
|
1530
|
+
controlPublications.set(producer, Object.freeze({ root: captured.original_root, content: captured.content }));
|
|
1531
|
+
return written;
|
|
1532
|
+
}
|
|
1533
|
+
export function writeRunRecipeContextExport(stateDir, producer) {
|
|
1534
|
+
const captured = withRunProducerEvidence(stateDir, producer, () => {
|
|
1535
|
+
try {
|
|
1536
|
+
return captureRecipe(stateDir, producer);
|
|
1537
|
+
}
|
|
1538
|
+
catch {
|
|
1539
|
+
return recipeUnavailable("recipe_metadata_invalid");
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
return captured.status === "unavailable" ? captured : publishContextView(stateDir, producer, "recipe", captured);
|
|
1543
|
+
}
|
|
1544
|
+
export function writeRunTraceContextExport(stateDir, producer) {
|
|
1545
|
+
const captured = withRunProducerEvidence(stateDir, producer, () => {
|
|
1546
|
+
try {
|
|
1547
|
+
return captureTrace(stateDir, producer);
|
|
1548
|
+
}
|
|
1549
|
+
catch {
|
|
1550
|
+
return traceUnavailable("trace_journal_unavailable");
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
if (captured.status === "unavailable")
|
|
1554
|
+
return captured;
|
|
1555
|
+
const written = publishContextView(stateDir, producer, "trace", captured);
|
|
1556
|
+
// A returned commit can retain cleanup failure separately; neither a throw nor that report mints admission.
|
|
1557
|
+
if (!readRunProducerCleanupFailure(producer))
|
|
1558
|
+
tracePublications.set(producer, Object.freeze({ root: captured.original_root, content: captured.content }));
|
|
1559
|
+
return written;
|
|
1560
|
+
}
|
|
1561
|
+
export function writeRunResultContextExport(stateDir, producer) {
|
|
1562
|
+
const captured = withRunProducerEvidence(stateDir, producer, () => captureResult(stateDir, producer));
|
|
1563
|
+
if (captured.status === "unavailable")
|
|
1564
|
+
return captured;
|
|
1565
|
+
const written = publishContextView(stateDir, producer, "result", captured);
|
|
1566
|
+
if (!readRunProducerCleanupFailure(producer))
|
|
1567
|
+
resultPublications.set(producer, Object.freeze({ root: captured.original_root, content: captured.content }));
|
|
1568
|
+
return written;
|
|
1569
|
+
}
|
|
1570
|
+
export function writeRunArtifactContextExport(stateDir, producer) {
|
|
1571
|
+
const captured = withRunProducerEvidence(stateDir, producer, () => {
|
|
1572
|
+
try {
|
|
1573
|
+
return captureArtifacts(stateDir, producer);
|
|
1574
|
+
}
|
|
1575
|
+
catch {
|
|
1576
|
+
return artifactUnavailable("artifact_observation_unavailable");
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
if (captured.status !== "unavailable")
|
|
1580
|
+
return publishContextView(stateDir, producer, "artifacts", captured);
|
|
1581
|
+
if (captured.reason !== "artifact_manifest_too_large")
|
|
1582
|
+
return captured;
|
|
1583
|
+
const inventoryView = retainArtifactInventoryView(stateDir, producer);
|
|
1584
|
+
return inventoryView ? publishContextView(stateDir, producer, "artifacts", inventoryView) : captured;
|
|
1585
|
+
}
|
|
1586
|
+
export function writeRunExecutionContextExport(stateDir, producer) {
|
|
1587
|
+
const captured = withRunProducerEvidence(stateDir, producer, () => {
|
|
1588
|
+
try {
|
|
1589
|
+
return captureExecution(stateDir, producer);
|
|
1590
|
+
}
|
|
1591
|
+
catch {
|
|
1592
|
+
return executionUnavailable("execution_unavailable");
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
if (captured.status === "unavailable")
|
|
1596
|
+
return captured;
|
|
1597
|
+
const written = publishContextView(stateDir, producer, "execution", captured);
|
|
1598
|
+
if (!readRunProducerCleanupFailure(producer))
|
|
1599
|
+
executionPublications.set(producer, Object.freeze({ root: captured.original_root, content: captured.content }));
|
|
1600
|
+
return written;
|
|
1601
|
+
}
|
|
1602
|
+
function readViewBinding(path, role) {
|
|
1603
|
+
const config = VIEW_CONFIG[role];
|
|
1604
|
+
if (config.format === "jsonl") {
|
|
1605
|
+
const read = readJsonlFileResilient(path, { maxBytes: config.maxBytes });
|
|
1606
|
+
return read.diagnostics.length ? undefined : record(read.records[0]);
|
|
1607
|
+
}
|
|
1608
|
+
const read = readJsonFileResilient(path, undefined, { maxBytes: config.maxBytes });
|
|
1609
|
+
return read.diagnostics.length ? undefined : record(read.value);
|
|
1610
|
+
}
|
|
1611
|
+
function publishContextView(stateDir, producer, role, captured) {
|
|
1612
|
+
const config = VIEW_CONFIG[role];
|
|
1613
|
+
return withRunProducerEvidence(stateDir, producer, () => {
|
|
1614
|
+
if (captured.unchanged && !captured.unchanged())
|
|
1615
|
+
throw new ContextExportBindingRefusal("Run context captured source binding changed.");
|
|
1616
|
+
const root = assertOwnedRunStateDirectory(stateDir, producer.run);
|
|
1617
|
+
const directory = lstatSync(root, { bigint: true });
|
|
1618
|
+
const metaPath = join(root, "run.json");
|
|
1619
|
+
const markerPath = join(root, RUN_STATE_OWNERSHIP_FILE);
|
|
1620
|
+
const meta = lstatSync(metaPath, { bigint: true });
|
|
1621
|
+
const marker = lstatSync(markerPath, { bigint: true });
|
|
1622
|
+
const evidencePath = join(root, "evidence");
|
|
1623
|
+
let evidence = optionalStamp(evidencePath);
|
|
1624
|
+
if (evidence && (!evidence.isDirectory() || evidence.isSymbolicLink()))
|
|
1625
|
+
throw new ContextExportBindingRefusal("Run context export directory is unavailable or invalid.");
|
|
1626
|
+
if (!evidence) {
|
|
1627
|
+
mkdirSync(evidencePath);
|
|
1628
|
+
evidence = lstatSync(evidencePath, { bigint: true });
|
|
1629
|
+
}
|
|
1630
|
+
const path = join(root, runContextExportPath(role));
|
|
1631
|
+
const target = optionalStamp(path);
|
|
1632
|
+
if (target) {
|
|
1633
|
+
if (!target.isFile())
|
|
1634
|
+
throw new ContextExportBindingRefusal("Run context export binding is unavailable or invalid.");
|
|
1635
|
+
const value = readViewBinding(path, role);
|
|
1636
|
+
const after = optionalStamp(path);
|
|
1637
|
+
if (!value || value.schema !== config.schema || value.run !== producer.run ||
|
|
1638
|
+
value.run_instance_id !== producer.runInstanceId || !after || !sameFile(target, after)) {
|
|
1639
|
+
throw new ContextExportBindingRefusal("Run context export binding changed or is unavailable.");
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
writeTextAtomic(path, captured.content, { onBeforeReplace() {
|
|
1643
|
+
const currentDirectory = lstatSync(root, { bigint: true });
|
|
1644
|
+
const currentEvidence = lstatSync(evidencePath, { bigint: true });
|
|
1645
|
+
const currentTarget = optionalStamp(path);
|
|
1646
|
+
if ((captured.unchanged && !captured.unchanged()) || !currentDirectory.isDirectory() || !sameObject(directory, currentDirectory) ||
|
|
1647
|
+
!currentEvidence.isDirectory() || !sameObject(evidence, currentEvidence) ||
|
|
1648
|
+
!sameFile(meta, lstatSync(metaPath, { bigint: true })) || !sameFile(marker, lstatSync(markerPath, { bigint: true })) ||
|
|
1649
|
+
(target ? !currentTarget || !sameFile(target, currentTarget) : currentTarget !== undefined)) {
|
|
1650
|
+
throw new ContextExportBindingRefusal("Run context export binding changed or is unavailable.");
|
|
1651
|
+
}
|
|
1652
|
+
} });
|
|
1653
|
+
const entry = Object.freeze({ availability: "available", path: runContextExportPath(role),
|
|
1654
|
+
media_type: config.media, bytes_total: Buffer.byteLength(captured.content, "utf8"), mutable: true,
|
|
1655
|
+
coverage: captured.coverage ?? config.coverage });
|
|
1656
|
+
return Object.freeze({ status: "written", captured_at: captured.captured_at, entry });
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
/** A separate admitted index commit preserves partial view effects; unavailable sources create metadata, never empty views. */
|
|
1660
|
+
export function writeRunControlContextIndex(stateDir, producer) {
|
|
1661
|
+
return publishContextIndex(stateDir, producer, "control", writeRunControlContextExport(stateDir, producer));
|
|
1662
|
+
}
|
|
1663
|
+
export function writeRunRecipeContextIndex(stateDir, producer) {
|
|
1664
|
+
return publishContextIndex(stateDir, producer, "recipe", writeRunRecipeContextExport(stateDir, producer));
|
|
1665
|
+
}
|
|
1666
|
+
export function writeRunTraceContextIndex(stateDir, producer) {
|
|
1667
|
+
return publishContextIndex(stateDir, producer, "trace", writeRunTraceContextExport(stateDir, producer));
|
|
1668
|
+
}
|
|
1669
|
+
export function writeRunResultContextIndex(stateDir, producer) {
|
|
1670
|
+
return publishContextIndex(stateDir, producer, "result", writeRunResultContextExport(stateDir, producer));
|
|
1671
|
+
}
|
|
1672
|
+
export function writeRunArtifactContextIndex(stateDir, producer) {
|
|
1673
|
+
return publishContextIndex(stateDir, producer, "artifacts", writeRunArtifactContextExport(stateDir, producer));
|
|
1674
|
+
}
|
|
1675
|
+
export function writeRunExecutionContextIndex(stateDir, producer) {
|
|
1676
|
+
return publishContextIndex(stateDir, producer, "execution", writeRunExecutionContextExport(stateDir, producer));
|
|
1677
|
+
}
|
|
1678
|
+
function publishContextIndex(stateDir, producer, role, view) {
|
|
1679
|
+
const entry = view.status === "written" ? view.entry
|
|
1680
|
+
: view.reason === "artifact_declarations_absent" ? { availability: "not_applicable", path: null }
|
|
1681
|
+
: view.reason === "control_journal_absent" || view.reason === "trace_journal_absent" || view.reason === "result_absent" || view.reason === "execution_absent" ? { availability: "pending", path: null }
|
|
1682
|
+
: { availability: "unavailable", path: null, reason: view.reason };
|
|
1683
|
+
return withRunProducerEvidence(stateDir, producer, () => {
|
|
1684
|
+
const expected = { run: producer.run, run_instance_id: producer.runInstanceId,
|
|
1685
|
+
...(producer.ownerId !== undefined ? { ownerId: producer.ownerId } : {}) };
|
|
1686
|
+
const publication = writeRunContextIndexUnderLock(stateDir, expected, { [role]: entry });
|
|
1687
|
+
return Object.freeze({ status: "published", ...publication });
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
function primarySourcePath(stateDir, identity) {
|
|
1691
|
+
const key = createHash("sha256").update(JSON.stringify([identity.run, identity.run_instance_id, identity.ownerId ?? null])).digest("hex");
|
|
1692
|
+
return join(stateDir, "evidence", `primary-${key}.txt`);
|
|
1693
|
+
}
|
|
1694
|
+
class PrimaryTextBindingRefusal extends Error {
|
|
1695
|
+
}
|
|
1696
|
+
function primaryUnavailable(reason) { return Object.freeze({ output: null, output_unavailable: reason }); }
|
|
1697
|
+
function readFixedSourceText(path, maxBytes = RUN_STREAM_SOURCE_MAX_BYTES, label = "Run primary source") {
|
|
1698
|
+
const stamp = lstatSync(path, { bigint: true });
|
|
1699
|
+
if (!stamp.isFile() || stamp.size > BigInt(maxBytes))
|
|
1700
|
+
throw new PrimaryTextBindingRefusal(`${label} is not a bounded regular file.`);
|
|
1701
|
+
const fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
|
|
1702
|
+
try {
|
|
1703
|
+
if (!sameFile(stamp, fstatSync(fd, { bigint: true })))
|
|
1704
|
+
throw new PrimaryTextBindingRefusal(`${label} changed.`);
|
|
1705
|
+
const bytes = Buffer.alloc(Number(stamp.size));
|
|
1706
|
+
let offset = 0;
|
|
1707
|
+
while (offset < bytes.length) {
|
|
1708
|
+
const count = readSync(fd, bytes, offset, bytes.length - offset, offset);
|
|
1709
|
+
if (!count)
|
|
1710
|
+
throw new PrimaryTextBindingRefusal(`${label} ended before its bounded read completed.`);
|
|
1711
|
+
offset += count;
|
|
1712
|
+
}
|
|
1713
|
+
let text;
|
|
1714
|
+
try {
|
|
1715
|
+
text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
1716
|
+
}
|
|
1717
|
+
catch (error) {
|
|
1718
|
+
throw new PrimaryTextBindingRefusal(`${label} is not valid UTF-8.`, { cause: error });
|
|
1719
|
+
}
|
|
1720
|
+
if (!sameFile(stamp, fstatSync(fd, { bigint: true })) || !sameFile(stamp, lstatSync(path, { bigint: true })))
|
|
1721
|
+
throw new PrimaryTextBindingRefusal(`${label} changed.`);
|
|
1722
|
+
return { text, stamp };
|
|
1723
|
+
}
|
|
1724
|
+
finally {
|
|
1725
|
+
closeSync(fd);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
const snapshotReferenceIssuer = Symbol("Run snapshot reference issuer");
|
|
1729
|
+
class SnapshotReferenceReader {
|
|
1730
|
+
#root;
|
|
1731
|
+
#identity;
|
|
1732
|
+
#frameUnchanged;
|
|
1733
|
+
read;
|
|
1734
|
+
constructor(issuer, root, identity, frameUnchanged, read) {
|
|
1735
|
+
if (issuer !== snapshotReferenceIssuer)
|
|
1736
|
+
throw new Error("Run snapshot reference reader requires its private issuer.");
|
|
1737
|
+
this.#root = root;
|
|
1738
|
+
this.#identity = identity;
|
|
1739
|
+
this.#frameUnchanged = frameUnchanged;
|
|
1740
|
+
this.read = read;
|
|
1741
|
+
Object.freeze(this);
|
|
1742
|
+
}
|
|
1743
|
+
static admit(value, stateDir, expected) {
|
|
1744
|
+
if (isPromise(value)) {
|
|
1745
|
+
void Promise.prototype.then.call(value, undefined, () => undefined);
|
|
1746
|
+
return undefined;
|
|
1747
|
+
}
|
|
1748
|
+
if (!value || typeof value !== "object" || !(#root in value))
|
|
1749
|
+
return undefined;
|
|
1750
|
+
const identity = contextReadIdentity(expected);
|
|
1751
|
+
return identity && value.#root === stateDir && identity.run === value.#identity.run && identity.run_instance_id === value.#identity.run_instance_id &&
|
|
1752
|
+
identity.ownerId === value.#identity.ownerId && value.#frameUnchanged() ? value : undefined;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
/** Original same-module reader only; schemas, copied objects/prototypes and flags are not permission. */
|
|
1756
|
+
export function admitRunSnapshotReferenceReader(stateDir, expected, value) {
|
|
1757
|
+
return SnapshotReferenceReader.admit(value, stateDir, expected);
|
|
1758
|
+
}
|
|
1759
|
+
function snapshotReferenceFrame(root) {
|
|
1760
|
+
const directory = lstatSync(root, { bigint: true });
|
|
1761
|
+
const markerPath = join(root, RUN_STATE_OWNERSHIP_FILE);
|
|
1762
|
+
const marker = lstatSync(markerPath, { bigint: true });
|
|
1763
|
+
return () => { try {
|
|
1764
|
+
const current = lstatSync(root, { bigint: true });
|
|
1765
|
+
return current.isDirectory() && sameObject(directory, current) && sameFile(marker, lstatSync(markerPath, { bigint: true }));
|
|
1766
|
+
}
|
|
1767
|
+
catch {
|
|
1768
|
+
return false;
|
|
1769
|
+
} };
|
|
1770
|
+
}
|
|
1771
|
+
/** Snapshot only own JSON data, without invoking serialization hooks/getters; refuse the complete privacy capture on loss. */
|
|
1772
|
+
function launchPrivacyMetadata(value) {
|
|
1773
|
+
try {
|
|
1774
|
+
let bytes = 0;
|
|
1775
|
+
const seen = new Set();
|
|
1776
|
+
const account = (count) => { bytes += count; if (bytes > RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES)
|
|
1777
|
+
throw new Error("Run launch privacy capture exceeds its budget."); };
|
|
1778
|
+
const copy = (item) => {
|
|
1779
|
+
if (item === null || typeof item === "string" || typeof item === "boolean" || (typeof item === "number" && Number.isFinite(item))) {
|
|
1780
|
+
account(Buffer.byteLength(JSON.stringify(item)));
|
|
1781
|
+
return item;
|
|
1782
|
+
}
|
|
1783
|
+
if (!item || typeof item !== "object" || seen.has(item))
|
|
1784
|
+
throw new Error("Run launch privacy capture is not JSON data.");
|
|
1785
|
+
const array = Array.isArray(item);
|
|
1786
|
+
const prototype = Object.getPrototypeOf(item);
|
|
1787
|
+
if (prototype !== null && prototype !== (array ? Array.prototype : Object.prototype))
|
|
1788
|
+
throw new Error("Run launch privacy capture has a custom prototype.");
|
|
1789
|
+
const fields = Object.getOwnPropertyDescriptors(item);
|
|
1790
|
+
if (fields.toJSON && (!Object.hasOwn(fields.toJSON, "value") || typeof fields.toJSON.value === "function"))
|
|
1791
|
+
throw new Error("Run launch privacy capture has a serialization hook.");
|
|
1792
|
+
seen.add(item);
|
|
1793
|
+
account(2);
|
|
1794
|
+
const result = array ? [] : Object.create(null);
|
|
1795
|
+
Object.setPrototypeOf(result, null);
|
|
1796
|
+
if (array) {
|
|
1797
|
+
const length = item.length;
|
|
1798
|
+
if (length > RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES)
|
|
1799
|
+
throw new Error("Run launch privacy array exceeds its budget.");
|
|
1800
|
+
for (let i = 0; i < length; i++) {
|
|
1801
|
+
const field = fields[String(i)];
|
|
1802
|
+
if (field && !Object.hasOwn(field, "value"))
|
|
1803
|
+
throw new Error("Run launch privacy capture has an accessor.");
|
|
1804
|
+
if (i)
|
|
1805
|
+
account(1);
|
|
1806
|
+
result[i] = copy(field?.value === undefined ? null : field.value);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
else {
|
|
1810
|
+
let count = 0;
|
|
1811
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
1812
|
+
if (!field.enumerable)
|
|
1813
|
+
continue;
|
|
1814
|
+
if (!Object.hasOwn(field, "value"))
|
|
1815
|
+
throw new Error("Run launch privacy capture has an accessor.");
|
|
1816
|
+
if (field.value === undefined)
|
|
1817
|
+
continue;
|
|
1818
|
+
account(Buffer.byteLength(JSON.stringify(key)) + 1 + (count++ ? 1 : 0));
|
|
1819
|
+
result[key] = copy(field.value);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
seen.delete(item);
|
|
1823
|
+
return result;
|
|
1824
|
+
};
|
|
1825
|
+
const copied = copy(value);
|
|
1826
|
+
return record(copied) ? JSON.parse(JSON.stringify(copied)) : undefined;
|
|
1827
|
+
}
|
|
1828
|
+
catch {
|
|
1829
|
+
return undefined;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
/** Trusted fresh-launch kernel entry, under its already-held lifecycle lock after full metadata assembly, before publication/work.
|
|
1833
|
+
* The caller's actual admission is authority, NOT metadata shape, marker or clock origin as a privacy certificate; not an arbitrary-JS sandbox.
|
|
1834
|
+
* No actor/configuration or saved-data route may invoke this issuer. The original runtime-local reader, never its DTO, crosses the handoff.
|
|
1835
|
+
*/
|
|
1836
|
+
export function captureRunAdmissionSnapshotReferences(stateDir, metadata, origin) {
|
|
1837
|
+
const data = contextReadRequest(metadata);
|
|
1838
|
+
const identity = contextReadIdentity(metadata);
|
|
1839
|
+
if (!data || !identity || data.pid !== 0 || data.outcome_protocol !== RUN_OUTCOME_PROTOCOL)
|
|
1840
|
+
throw new ContextExportBindingRefusal("Run launch reference admission is invalid.");
|
|
1841
|
+
const root = assertOwnedRunStateDirectory(stateDir, identity.run);
|
|
1842
|
+
if (data.state_dir !== root || readRunContextIdentity(root, identity).state === "available")
|
|
1843
|
+
throw new ContextExportBindingRefusal("Run launch reference admission must be unpublished and root-bound.");
|
|
1844
|
+
const policy = contextReadRequest(data.supervision);
|
|
1845
|
+
let clock;
|
|
1846
|
+
try {
|
|
1847
|
+
clock = policy && readRunSupervisionClock({ started_at: data.started_at, createdAt: data.createdAt, supervision: policy });
|
|
1848
|
+
}
|
|
1849
|
+
catch { /* Fail closed below. */ }
|
|
1850
|
+
if (!clock || createRunSupervisionTimebase(clock, origin).state !== "available")
|
|
1851
|
+
throw new ContextExportBindingRefusal("Run launch reference admission requires its original clock origin.");
|
|
1852
|
+
return capturedSnapshotReferenceReader({ root, metadata: launchPrivacyMetadata(metadata), frameUnchanged: snapshotReferenceFrame(root) }, identity);
|
|
1853
|
+
}
|
|
1854
|
+
function snapshotReference(value, redact, withoutSecrets) {
|
|
1855
|
+
if (typeof value !== "string" || !isAbsolute(value) || /[\u0000-\u001f\u007f]/u.test(value) || Buffer.from(value).toString("utf8") !== value)
|
|
1856
|
+
return "invalid";
|
|
1857
|
+
return withoutSecrets(value) === value && redact(value) === value ? "available" : "redacted";
|
|
1858
|
+
}
|
|
1859
|
+
/** Private runtime-local reference permission from genuine admission; not lifecycle/timing/body authority or durable transport. */
|
|
1860
|
+
export function createRunSnapshotReferenceReader(stateDir, producer) {
|
|
1861
|
+
const capture = withRunProducerEvidence(stateDir, producer, () => {
|
|
1862
|
+
const root = assertOwnedRunStateDirectory(stateDir, producer.run);
|
|
1863
|
+
const metadata = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
1864
|
+
if (metadata && (metadata.state_dir !== root || metadata.run !== producer.run || metadata.run_instance_id !== producer.runInstanceId || metadata.ownerId !== producer.ownerId)) {
|
|
1865
|
+
throw new ContextExportBindingRefusal("Run snapshot reference capture binding changed.");
|
|
1866
|
+
}
|
|
1867
|
+
return { root, metadata, frameUnchanged: snapshotReferenceFrame(root) };
|
|
1868
|
+
});
|
|
1869
|
+
const identity = Object.freeze({ run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) });
|
|
1870
|
+
return capturedSnapshotReferenceReader(capture, identity);
|
|
1871
|
+
}
|
|
1872
|
+
function capturedSnapshotReferenceReader(capture, identity) {
|
|
1873
|
+
const indexPath = join(capture.root, RUN_CONTEXT_INDEX_FILE);
|
|
1874
|
+
let cwd = null;
|
|
1875
|
+
let cwdUnavailable;
|
|
1876
|
+
let indexUnavailable;
|
|
1877
|
+
let policyAvailable = false;
|
|
1878
|
+
try {
|
|
1879
|
+
if (capture.metadata) {
|
|
1880
|
+
const redact = createRunRecipeTextRedactor(capture.metadata), withoutSecrets = capturedSecretMask(capture.metadata, "");
|
|
1881
|
+
const cwdState = snapshotReference(capture.metadata.cwd, redact, withoutSecrets);
|
|
1882
|
+
if (cwdState === "available")
|
|
1883
|
+
cwd = capture.metadata.cwd;
|
|
1884
|
+
else
|
|
1885
|
+
cwdUnavailable = cwdState === "redacted" ? "cwd_reference_redacted" : "cwd_reference_invalid";
|
|
1886
|
+
const indexState = snapshotReference(indexPath, redact, withoutSecrets);
|
|
1887
|
+
if (indexState !== "available")
|
|
1888
|
+
indexUnavailable = indexState === "redacted" ? "index_reference_redacted" : "index_reference_invalid";
|
|
1889
|
+
policyAvailable = true;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
catch { /* Failed full admission privacy cannot disclose a reference or fall back to live metadata. */ }
|
|
1893
|
+
return new SnapshotReferenceReader(snapshotReferenceIssuer, capture.root, identity, capture.frameUnchanged, () => {
|
|
1894
|
+
const unavailable = (reason) => Object.freeze({ state: "unavailable", reason });
|
|
1895
|
+
if (!policyAvailable)
|
|
1896
|
+
return unavailable("privacy_capture_unavailable");
|
|
1897
|
+
if (!capture.frameUnchanged())
|
|
1898
|
+
return unavailable("observation_changed");
|
|
1899
|
+
const owner = readRunContextIdentity(capture.root, identity);
|
|
1900
|
+
if (owner.state !== "available")
|
|
1901
|
+
return unavailable(owner.reason);
|
|
1902
|
+
try {
|
|
1903
|
+
const maximum = { state: "available", identity, references: { cwd, ...(cwdUnavailable ? { cwd_unavailable: cwdUnavailable } : {}),
|
|
1904
|
+
context_index: indexUnavailable ? null : { identity, path: indexPath }, ...(indexUnavailable ? { context_index_unavailable: indexUnavailable } : {}) } };
|
|
1905
|
+
if (Buffer.byteLength(JSON.stringify(maximum)) > RUN_DELIVERY_MODEL_MAX_BYTES)
|
|
1906
|
+
return unavailable("references_too_large");
|
|
1907
|
+
const before = indexUnavailable ? undefined : optionalStamp(indexPath);
|
|
1908
|
+
const context = indexUnavailable ? undefined : readRunContextIndex(capture.root, identity);
|
|
1909
|
+
const unchanged = () => {
|
|
1910
|
+
try {
|
|
1911
|
+
const after = indexUnavailable ? undefined : optionalStamp(indexPath);
|
|
1912
|
+
return capture.frameUnchanged() && owner.unchanged() && (indexUnavailable !== undefined ||
|
|
1913
|
+
(before === undefined ? after === undefined : after !== undefined && sameObject(before, after) && before.mode === after.mode && before.size === after.size && before.mtimeNs === after.mtimeNs && before.ctimeNs === after.ctimeNs));
|
|
1914
|
+
}
|
|
1915
|
+
catch {
|
|
1916
|
+
return false;
|
|
1917
|
+
}
|
|
1918
|
+
};
|
|
1919
|
+
if (!unchanged())
|
|
1920
|
+
return unavailable("observation_changed");
|
|
1921
|
+
const references = Object.freeze({ cwd, ...(cwdUnavailable ? { cwd_unavailable: cwdUnavailable } : {}),
|
|
1922
|
+
context_index: context?.state === "available" ? Object.freeze({ identity: context.identity, path: context.local_path }) : null,
|
|
1923
|
+
...(context?.state === "available" ? {} : { context_index_unavailable: indexUnavailable ?? "index_unavailable" }) });
|
|
1924
|
+
return Object.freeze({ state: "available", identity, references, unchanged });
|
|
1925
|
+
}
|
|
1926
|
+
catch {
|
|
1927
|
+
return unavailable("observation_unavailable");
|
|
1928
|
+
}
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
/** Kernel-owned invocation context. Only explicit executor selection publishes command text or its final response. */
|
|
1932
|
+
export function createRunPrimaryTextPublisher(stateDir, producer) {
|
|
1933
|
+
const metadata = withRunProducerEvidence(stateDir, producer, () => readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES));
|
|
1934
|
+
let redact;
|
|
1935
|
+
try {
|
|
1936
|
+
if (metadata)
|
|
1937
|
+
redact = createRunRecipeTextRedactor(metadata);
|
|
1938
|
+
}
|
|
1939
|
+
catch { /* No privacy proof means no source publication. */ }
|
|
1940
|
+
const identityUnchanged = metadata ? createContextIdentityPrivacyCheck(metadata)({ run: producer.run, run_instance_id: producer.runInstanceId,
|
|
1941
|
+
...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) }) : undefined;
|
|
1942
|
+
let observed;
|
|
1943
|
+
let count = 0;
|
|
1944
|
+
let binding = primaryUnavailable("not_selected");
|
|
1945
|
+
let failure;
|
|
1946
|
+
let cleanupFailure;
|
|
1947
|
+
let sourceUnchanged;
|
|
1948
|
+
let responseSelected = false;
|
|
1949
|
+
const capture = (original, inputUnchanged, response) => {
|
|
1950
|
+
if (!redact) {
|
|
1951
|
+
binding = primaryUnavailable("privacy_capture_unavailable");
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
if (!identityUnchanged) {
|
|
1955
|
+
binding = primaryUnavailable("identity_redacted");
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
let projected;
|
|
1959
|
+
try {
|
|
1960
|
+
projected = redact(original);
|
|
1961
|
+
}
|
|
1962
|
+
catch (error) {
|
|
1963
|
+
failure = error;
|
|
1964
|
+
binding = primaryUnavailable("projection_unavailable");
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
if (typeof projected !== "string" || (response !== undefined && Buffer.from(projected).toString("utf8") !== projected) || Buffer.byteLength(projected) > RUN_STREAM_SOURCE_MAX_BYTES) {
|
|
1968
|
+
binding = primaryUnavailable("projection_unavailable");
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1971
|
+
const text = projected;
|
|
1972
|
+
let chars = 0;
|
|
1973
|
+
let preview = "";
|
|
1974
|
+
let shown = 0;
|
|
1975
|
+
for (const point of text) {
|
|
1976
|
+
chars++;
|
|
1977
|
+
if (shown < COMPACT_PREVIEW_CHARS) {
|
|
1978
|
+
preview += point;
|
|
1979
|
+
shown++;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
const path = primarySourcePath(stateDir, { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) });
|
|
1983
|
+
const output = Object.freeze({ source: Object.freeze({ path, run_instance_id: producer.runInstanceId, sha256: createHash("sha256").update(text).digest("hex") }),
|
|
1984
|
+
bytes_total: Buffer.byteLength(text), shown_bytes: Object.freeze({ start: 0, end: Buffer.byteLength(preview) }),
|
|
1985
|
+
chars_total: chars, chars_shown: shown, chars_omitted: chars - shown, preview, truncated: shown < chars, redacted: text !== original, ...response });
|
|
1986
|
+
if (Buffer.byteLength(JSON.stringify({ output })) > RUN_DELIVERY_MODEL_MAX_BYTES) {
|
|
1987
|
+
binding = primaryUnavailable("descriptor_too_large");
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
binding = withRunProducerEvidence(stateDir, producer, () => {
|
|
1991
|
+
if (!inputUnchanged())
|
|
1992
|
+
throw new PrimaryTextBindingRefusal("Run primary selected capture binding changed.");
|
|
1993
|
+
const root = assertOwnedRunStateDirectory(stateDir, producer.run);
|
|
1994
|
+
const directory = lstatSync(root, { bigint: true });
|
|
1995
|
+
const metaPath = join(root, "run.json");
|
|
1996
|
+
const markerPath = join(root, RUN_STATE_OWNERSHIP_FILE);
|
|
1997
|
+
const meta = lstatSync(metaPath, { bigint: true });
|
|
1998
|
+
const marker = lstatSync(markerPath, { bigint: true });
|
|
1999
|
+
const evidencePath = join(root, "evidence");
|
|
2000
|
+
let evidence;
|
|
2001
|
+
const rootUnchanged = () => {
|
|
2002
|
+
try {
|
|
2003
|
+
return sameObject(directory, lstatSync(root, { bigint: true })) && sameFile(meta, lstatSync(metaPath, { bigint: true })) && sameFile(marker, lstatSync(markerPath, { bigint: true }));
|
|
2004
|
+
}
|
|
2005
|
+
catch {
|
|
2006
|
+
return false;
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
const frameUnchanged = () => {
|
|
2010
|
+
try {
|
|
2011
|
+
return rootUnchanged() && !!evidence && sameObject(evidence, lstatSync(evidencePath, { bigint: true }));
|
|
2012
|
+
}
|
|
2013
|
+
catch {
|
|
2014
|
+
return false;
|
|
2015
|
+
}
|
|
2016
|
+
};
|
|
2017
|
+
try {
|
|
2018
|
+
evidence = optionalStamp(evidencePath);
|
|
2019
|
+
if (evidence && (!evidence.isDirectory() || evidence.isSymbolicLink()))
|
|
2020
|
+
throw new PrimaryTextBindingRefusal("Run primary export directory binding is invalid.");
|
|
2021
|
+
if (!evidence) {
|
|
2022
|
+
mkdirSync(evidencePath);
|
|
2023
|
+
evidence = lstatSync(evidencePath, { bigint: true });
|
|
2024
|
+
}
|
|
2025
|
+
const existing = optionalStamp(path);
|
|
2026
|
+
if (existing) {
|
|
2027
|
+
if (readFixedSourceText(path).text !== text)
|
|
2028
|
+
throw new PrimaryTextBindingRefusal("Run primary source identity already fixes different bytes.");
|
|
2029
|
+
}
|
|
2030
|
+
else {
|
|
2031
|
+
const temporary = join(evidencePath, `.primary-${randomUUID()}.tmp`);
|
|
2032
|
+
let written = false;
|
|
2033
|
+
let failed = false;
|
|
2034
|
+
try {
|
|
2035
|
+
writeFileSync(temporary, text, { flag: "wx", mode: 0o600 });
|
|
2036
|
+
written = true;
|
|
2037
|
+
if (!frameUnchanged() || !inputUnchanged())
|
|
2038
|
+
throw new PrimaryTextBindingRefusal("Run primary publication binding changed.");
|
|
2039
|
+
try {
|
|
2040
|
+
linkSync(temporary, path);
|
|
2041
|
+
}
|
|
2042
|
+
catch (error) {
|
|
2043
|
+
if (error.code === "EEXIST")
|
|
2044
|
+
throw new PrimaryTextBindingRefusal("Run primary source target appeared during publication.", { cause: error });
|
|
2045
|
+
throw error;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
catch (error) {
|
|
2049
|
+
failed = true;
|
|
2050
|
+
throw error;
|
|
2051
|
+
}
|
|
2052
|
+
finally {
|
|
2053
|
+
if (written) {
|
|
2054
|
+
try {
|
|
2055
|
+
unlinkSync(temporary);
|
|
2056
|
+
}
|
|
2057
|
+
catch (error) {
|
|
2058
|
+
cleanupFailure ??= error;
|
|
2059
|
+
if (!failed)
|
|
2060
|
+
throw error;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
const saved = readFixedSourceText(path);
|
|
2066
|
+
if (saved.text !== text)
|
|
2067
|
+
throw new PrimaryTextBindingRefusal("Run primary source identity already fixes different bytes.");
|
|
2068
|
+
sourceUnchanged = () => { try {
|
|
2069
|
+
return frameUnchanged() && sameFile(saved.stamp, lstatSync(path, { bigint: true }));
|
|
2070
|
+
}
|
|
2071
|
+
catch {
|
|
2072
|
+
return false;
|
|
2073
|
+
} };
|
|
2074
|
+
}
|
|
2075
|
+
catch (error) {
|
|
2076
|
+
if (error instanceof PrimaryTextBindingRefusal)
|
|
2077
|
+
throw error;
|
|
2078
|
+
failure = error;
|
|
2079
|
+
if (!rootUnchanged() || (evidence && !frameUnchanged()) || !inputUnchanged())
|
|
2080
|
+
throw new PrimaryTextBindingRefusal("Run primary publication binding changed during export failure.");
|
|
2081
|
+
return primaryUnavailable("export_failed");
|
|
2082
|
+
}
|
|
2083
|
+
if (!frameUnchanged() || !inputUnchanged())
|
|
2084
|
+
throw new PrimaryTextBindingRefusal("Run primary publication binding changed.");
|
|
2085
|
+
return Object.freeze({ output });
|
|
2086
|
+
});
|
|
2087
|
+
};
|
|
2088
|
+
return Object.freeze({
|
|
2089
|
+
observe(result, observation) {
|
|
2090
|
+
count++;
|
|
2091
|
+
observed = { result, observation: { commandId: observation.commandId, attempts: [...observation.attempts], session: observation.session } };
|
|
2092
|
+
},
|
|
2093
|
+
select(result) {
|
|
2094
|
+
if (!observed || result !== observed.result)
|
|
2095
|
+
throw new PrimaryTextBindingRefusal("Run primary selection requires its exact observed invocation result.");
|
|
2096
|
+
withRunProducerEvidence(stateDir, producer, () => { });
|
|
2097
|
+
const { commandId, attempts, session } = observed.observation;
|
|
2098
|
+
const receipt = attempts[0];
|
|
2099
|
+
const selected = result;
|
|
2100
|
+
const raw = receipt?.result;
|
|
2101
|
+
if (count !== 1 || session || !/^command-[0-9]{3,}$/.test(commandId) || attempts.length !== 1 || receipt?.attempt !== 1 ||
|
|
2102
|
+
selected.code !== 0 || selected.killed || raw?.code !== 0 || raw.killed) {
|
|
2103
|
+
binding = primaryUnavailable("selection_not_supported");
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
if (!redact) {
|
|
2107
|
+
binding = primaryUnavailable("privacy_capture_unavailable");
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
if (!identityUnchanged) {
|
|
2111
|
+
binding = primaryUnavailable("identity_redacted");
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
const relative = `captures/${commandId}/attempt-001/stdout.log`;
|
|
2115
|
+
if ((raw.stdoutFile !== undefined && raw.stdoutFile !== join(stateDir, relative)) || selected.stdout !== raw.stdout) {
|
|
2116
|
+
throw new PrimaryTextBindingRefusal("Run primary selected capture binding changed.");
|
|
2117
|
+
}
|
|
2118
|
+
const observedBytes = raw.stdoutBytes;
|
|
2119
|
+
const memoryBytes = Buffer.byteLength(raw.stdout);
|
|
2120
|
+
const tail = raw.stdoutTruncated === true;
|
|
2121
|
+
if (!raw.stdoutFile || raw.stdoutCaptureIncomplete ||
|
|
2122
|
+
(raw.stdoutFileBytes !== undefined && raw.stdoutFileBytes !== observedBytes) ||
|
|
2123
|
+
typeof observedBytes !== "number" || !Number.isSafeInteger(observedBytes) || observedBytes < memoryBytes ||
|
|
2124
|
+
(tail ? observedBytes <= memoryBytes || typeof receipt.stdoutSha256 !== "string" || receipt.stdoutSha256.length !== 64 || !/^[a-f0-9]{64}$/.test(receipt.stdoutSha256)
|
|
2125
|
+
: observedBytes !== memoryBytes)) {
|
|
2126
|
+
binding = primaryUnavailable("complete_capture_unavailable");
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
const input = readExecutionCaptureText(stateDir, relative);
|
|
2130
|
+
if (input.availability !== "available") {
|
|
2131
|
+
binding = primaryUnavailable("capture_unavailable");
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
// A matching tail/size cannot bind an unseen prefix; compare the privately retained observed-stream digest.
|
|
2135
|
+
if (input.reference.bytes_total !== observedBytes || (tail
|
|
2136
|
+
? !input.text.endsWith(raw.stdout) || createHash("sha256").update(input.text).digest("hex") !== receipt.stdoutSha256
|
|
2137
|
+
: input.text !== raw.stdout))
|
|
2138
|
+
throw new PrimaryTextBindingRefusal("Run primary selected capture binding changed.");
|
|
2139
|
+
if (!input.text) {
|
|
2140
|
+
binding = primaryUnavailable("no_primary_text");
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
capture(input.text, () => input.reference.unchanged());
|
|
2144
|
+
},
|
|
2145
|
+
selectResponse(text, upstreamTruncated) {
|
|
2146
|
+
withRunProducerEvidence(stateDir, producer, () => { });
|
|
2147
|
+
if (responseSelected)
|
|
2148
|
+
throw new PrimaryTextBindingRefusal("Run final response is already selected.");
|
|
2149
|
+
// Never replace a complete source or heal failed privacy/capture/publication through another selection.
|
|
2150
|
+
if (binding.output || !["not_selected", "selection_not_supported"].includes(binding.output_unavailable))
|
|
2151
|
+
return;
|
|
2152
|
+
responseSelected = true;
|
|
2153
|
+
if (typeof text !== "string" || typeof upstreamTruncated !== "boolean")
|
|
2154
|
+
throw new PrimaryTextBindingRefusal("Run final response selection is invalid.");
|
|
2155
|
+
if (!text) {
|
|
2156
|
+
binding = primaryUnavailable("no_primary_text");
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
if (Buffer.byteLength(text) > RUN_STREAM_SOURCE_MAX_BYTES) {
|
|
2160
|
+
binding = primaryUnavailable("response_too_large");
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
// The executor supplies an immutable call-local response, not a session log or a reconstructed capture.
|
|
2164
|
+
capture(text, () => true, { text_basis: "actor_response", upstream_truncated: upstreamTruncated });
|
|
2165
|
+
},
|
|
2166
|
+
snapshot: () => {
|
|
2167
|
+
if (binding.output && !sourceUnchanged?.())
|
|
2168
|
+
throw new PrimaryTextBindingRefusal("Run primary retained source binding changed.");
|
|
2169
|
+
return binding;
|
|
2170
|
+
},
|
|
2171
|
+
readFailure: () => failure === undefined && cleanupFailure === undefined ? undefined : Object.freeze({ error: failure, cleanup_error: cleanupFailure }),
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
function primaryReadUnavailable(reason, output_unavailable) {
|
|
2175
|
+
return Object.freeze({ state: "unavailable", reason, ...(output_unavailable === undefined ? {} : { output_unavailable }) });
|
|
2176
|
+
}
|
|
2177
|
+
function contextReadRequest(value) {
|
|
2178
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
2179
|
+
return undefined;
|
|
2180
|
+
const fields = Object.getOwnPropertyDescriptors(value);
|
|
2181
|
+
return Object.values(fields).every(field => Object.hasOwn(field, "value"))
|
|
2182
|
+
? Object.fromEntries(Object.entries(fields).map(([key, field]) => [key, field.value])) : undefined;
|
|
2183
|
+
}
|
|
2184
|
+
function contextReadIdentity(expected) {
|
|
2185
|
+
const value = contextReadRequest(expected);
|
|
2186
|
+
if (!value || typeof value.run !== "string" || !value.run || /[^A-Za-z0-9_.-]/u.test(value.run) ||
|
|
2187
|
+
typeof value.run_instance_id !== "string" || !value.run_instance_id ||
|
|
2188
|
+
(value.ownerId !== undefined && (typeof value.ownerId !== "string" || !value.ownerId)))
|
|
2189
|
+
return undefined;
|
|
2190
|
+
return Object.freeze({ run: value.run, run_instance_id: value.run_instance_id, ...(value.ownerId === undefined ? {} : { ownerId: value.ownerId }) });
|
|
2191
|
+
}
|
|
2192
|
+
function contextJson(path) {
|
|
2193
|
+
const source = readFixedSourceText(path, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
2194
|
+
const value = record(JSON.parse(source.text.startsWith("\uFEFF") ? source.text.slice(1) : source.text));
|
|
2195
|
+
if (!value)
|
|
2196
|
+
throw new Error("Run primary metadata is not an object.");
|
|
2197
|
+
return { value, stamp: source.stamp };
|
|
2198
|
+
}
|
|
2199
|
+
function primaryChars(text) { let count = 0; for (const _point of text)
|
|
2200
|
+
count++; return count; }
|
|
2201
|
+
function primaryBoundary(bytes, offset) {
|
|
2202
|
+
return offset === 0 || offset === bytes.length || (bytes[offset] & 0xc0) !== 0x80;
|
|
2203
|
+
}
|
|
2204
|
+
/** Saved canonical facts authorize only this published source. No producer, index, raw discovery, refresh, work or ACK. */
|
|
2205
|
+
export function readRunPrimaryTextSource(stateDir, expected, query) {
|
|
2206
|
+
return readPrimaryTextSource(stateDir, expected, query, false);
|
|
2207
|
+
}
|
|
2208
|
+
/** Fresh completion admission only: select bounded text from the same verified source observation, not another source read. */
|
|
2209
|
+
export function readRunCompletionTextSource(stateDir, expected) {
|
|
2210
|
+
return readPrimaryTextSource(stateDir, expected, undefined, true);
|
|
2211
|
+
}
|
|
2212
|
+
function readPrimaryTextSource(stateDir, expected, query, completion) {
|
|
2213
|
+
const request = contextReadIdentity(expected);
|
|
2214
|
+
if (!request)
|
|
2215
|
+
return primaryReadUnavailable("selection_invalid");
|
|
2216
|
+
let range;
|
|
2217
|
+
if (query !== undefined) {
|
|
2218
|
+
const value = contextReadRequest(query);
|
|
2219
|
+
if (!value || Object.keys(value).some(key => key !== "start" && key !== "end") ||
|
|
2220
|
+
!Number.isSafeInteger(value.start) || !Number.isSafeInteger(value.end) || Number(value.start) < 0 || Number(value.end) < Number(value.start)) {
|
|
2221
|
+
return primaryReadUnavailable("selection_invalid");
|
|
2222
|
+
}
|
|
2223
|
+
range = { start: Number(value.start), end: Number(value.end) };
|
|
2224
|
+
if (range.end - range.start > RUN_CONTEXT_RESULT_MAX_BYTES)
|
|
2225
|
+
return primaryReadUnavailable("range_too_large");
|
|
2226
|
+
}
|
|
2227
|
+
const owner = readRunContextIdentity(stateDir, request);
|
|
2228
|
+
if (owner.state === "unavailable")
|
|
2229
|
+
return primaryReadUnavailable(owner.reason);
|
|
2230
|
+
let reason = "result_unavailable";
|
|
2231
|
+
try {
|
|
2232
|
+
const root = owner.local_path;
|
|
2233
|
+
const metadata = contextJson(join(root, "run.json"));
|
|
2234
|
+
const meta = metadata.value;
|
|
2235
|
+
if (meta.outcome_protocol !== RUN_OUTCOME_PROTOCOL || !Number.isSafeInteger(meta.pid) || Number(meta.pid) <= 0)
|
|
2236
|
+
return primaryReadUnavailable("publication_unavailable");
|
|
2237
|
+
const resultPath = join(root, "result.json");
|
|
2238
|
+
const canonical = contextJson(resultPath);
|
|
2239
|
+
const snapshot = readRunNaturalOutcomeSnapshot(root, meta, () => canonical.value);
|
|
2240
|
+
const frameUnchanged = () => owner.unchanged() && sameFile(metadata.stamp, lstatSync(join(root, "run.json"), { bigint: true })) &&
|
|
2241
|
+
sameFile(canonical.stamp, lstatSync(resultPath, { bigint: true }));
|
|
2242
|
+
if (!frameUnchanged())
|
|
2243
|
+
return primaryReadUnavailable("observation_changed");
|
|
2244
|
+
if (!snapshot.result || (snapshot.outcome.state !== "accepted" && snapshot.outcome.state !== "captured"))
|
|
2245
|
+
return primaryReadUnavailable("result_unavailable");
|
|
2246
|
+
if (snapshot.result.primary_text_protocol !== RUN_PRIMARY_TEXT_PROTOCOL)
|
|
2247
|
+
return primaryReadUnavailable("publication_unavailable");
|
|
2248
|
+
let binding;
|
|
2249
|
+
try {
|
|
2250
|
+
binding = decodeRunPrimaryTextBinding(root, owner.identity.run_instance_id, snapshot.result);
|
|
2251
|
+
}
|
|
2252
|
+
catch {
|
|
2253
|
+
return primaryReadUnavailable("binding_invalid");
|
|
2254
|
+
}
|
|
2255
|
+
if (!binding.output)
|
|
2256
|
+
return primaryReadUnavailable("output_unavailable", binding.output_unavailable);
|
|
2257
|
+
const output = binding.output;
|
|
2258
|
+
if (snapshot.result.code !== 0 || (snapshot.result.killed === true && output.text_basis !== "actor_response") || output.bytes_total === 0 ||
|
|
2259
|
+
output.source.path !== primarySourcePath(root, owner.identity))
|
|
2260
|
+
return primaryReadUnavailable("binding_invalid");
|
|
2261
|
+
reason = "source_unavailable";
|
|
2262
|
+
const evidencePath = join(root, "evidence");
|
|
2263
|
+
const evidence = lstatSync(evidencePath, { bigint: true });
|
|
2264
|
+
if (!evidence.isDirectory() || evidence.isSymbolicLink())
|
|
2265
|
+
return primaryReadUnavailable("source_unavailable");
|
|
2266
|
+
const source = readFixedSourceText(output.source.path);
|
|
2267
|
+
const bytes = Buffer.from(source.text);
|
|
2268
|
+
const excerpt = output.shown_bytes;
|
|
2269
|
+
if (bytes.length !== output.bytes_total || primaryChars(source.text) !== output.chars_total ||
|
|
2270
|
+
createHash("sha256").update(bytes).digest("hex") !== output.source.sha256 ||
|
|
2271
|
+
!primaryBoundary(bytes, excerpt.start) || !primaryBoundary(bytes, excerpt.end) ||
|
|
2272
|
+
bytes.subarray(excerpt.start, excerpt.end).toString("utf8") !== output.preview)
|
|
2273
|
+
return primaryReadUnavailable("source_invalid");
|
|
2274
|
+
const unchanged = () => {
|
|
2275
|
+
try {
|
|
2276
|
+
const current = lstatSync(evidencePath, { bigint: true });
|
|
2277
|
+
return frameUnchanged() && current.isDirectory() && sameObject(evidence, current) &&
|
|
2278
|
+
sameFile(source.stamp, lstatSync(output.source.path, { bigint: true }));
|
|
2279
|
+
}
|
|
2280
|
+
catch {
|
|
2281
|
+
return false;
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
if (!unchanged())
|
|
2285
|
+
return primaryReadUnavailable("observation_changed");
|
|
2286
|
+
let selected = output;
|
|
2287
|
+
if (completion) {
|
|
2288
|
+
let end = Math.min(bytes.length, RUN_COMPLETION_MODEL_MAX_BYTES);
|
|
2289
|
+
while (!primaryBoundary(bytes, end))
|
|
2290
|
+
end--;
|
|
2291
|
+
if (end < bytes.length && bytes[end - 1] === 13 && bytes[end] === 10)
|
|
2292
|
+
end--;
|
|
2293
|
+
range = { start: 0, end };
|
|
2294
|
+
}
|
|
2295
|
+
if (range) {
|
|
2296
|
+
if (range.end > bytes.length || !primaryBoundary(bytes, range.start) || !primaryBoundary(bytes, range.end))
|
|
2297
|
+
return primaryReadUnavailable("range_invalid");
|
|
2298
|
+
const preview = bytes.subarray(range.start, range.end).toString("utf8");
|
|
2299
|
+
const shown = primaryChars(preview);
|
|
2300
|
+
selected = Object.freeze({ ...output, shown_bytes: Object.freeze(range), preview, chars_shown: shown, chars_omitted: output.chars_total - shown, truncated: shown < output.chars_total });
|
|
2301
|
+
}
|
|
2302
|
+
const result = Object.freeze({ state: "available", identity: owner.identity, result_acceptance: snapshot.outcome.state === "accepted" ? "natural" : "capture_only",
|
|
2303
|
+
selection: range ? "byte_range" : "recorded_preview", output: selected,
|
|
2304
|
+
...(completion ? { original_output: output } : {}), unchanged });
|
|
2305
|
+
// Completion text is private admission data, not a public64KiB encoded tool view.
|
|
2306
|
+
// Its48KiB prefix can need sixfold JSON escaping; the existing journal bound still limits this DTO.
|
|
2307
|
+
const encodedLimit = completion ? RUN_DELIVERY_JOURNAL_MAX_BYTES : RUN_CONTEXT_RESULT_MAX_BYTES;
|
|
2308
|
+
if (Buffer.byteLength(JSON.stringify(result)) > encodedLimit)
|
|
2309
|
+
return primaryReadUnavailable("range_too_large");
|
|
2310
|
+
return unchanged() ? result : primaryReadUnavailable("observation_changed");
|
|
2311
|
+
}
|
|
2312
|
+
catch {
|
|
2313
|
+
return primaryReadUnavailable(reason);
|
|
2314
|
+
}
|
|
2315
|
+
}
|