@tiphys/kernel 0.0.0 → 0.2.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/AGENTS.md +663 -0
- package/LICENSE +202 -0
- package/assurance-modes.yaml +299 -0
- package/checklists/clean-room.yaml +325 -0
- package/checklists/env-failure-diagnosis.yaml +68 -0
- package/checklists/flake-playbook.yaml +68 -0
- package/checklists/hazard-review.yaml +144 -0
- package/checklists/plan-review.yaml +103 -0
- package/dist/bin/tiphys.d.ts +2 -0
- package/dist/bin/tiphys.js +92 -0
- package/dist/src/adapters/load.d.ts +202 -0
- package/dist/src/adapters/load.js +440 -0
- package/dist/src/brief.d.ts +32 -0
- package/dist/src/brief.js +66 -0
- package/dist/src/checklists.d.ts +169 -0
- package/dist/src/checklists.js +310 -0
- package/dist/src/checks.d.ts +1539 -0
- package/dist/src/checks.js +5025 -0
- package/dist/src/cli.d.ts +9 -0
- package/dist/src/cli.js +68 -0
- package/dist/src/commands/brief.d.ts +92 -0
- package/dist/src/commands/brief.js +343 -0
- package/dist/src/commands/checklist.d.ts +42 -0
- package/dist/src/commands/checklist.js +168 -0
- package/dist/src/commands/cutover.d.ts +35 -0
- package/dist/src/commands/cutover.js +448 -0
- package/dist/src/commands/doctor.d.ts +264 -0
- package/dist/src/commands/doctor.js +1546 -0
- package/dist/src/commands/gates.d.ts +9 -0
- package/dist/src/commands/gates.js +360 -0
- package/dist/src/commands/init.d.ts +30 -0
- package/dist/src/commands/init.js +177 -0
- package/dist/src/commands/lock.d.ts +34 -0
- package/dist/src/commands/lock.js +340 -0
- package/dist/src/commands/mode.d.ts +40 -0
- package/dist/src/commands/mode.js +134 -0
- package/dist/src/commands/next.d.ts +130 -0
- package/dist/src/commands/next.js +597 -0
- package/dist/src/commands/plan.d.ts +20 -0
- package/dist/src/commands/plan.js +105 -0
- package/dist/src/commands/pool.d.ts +1 -0
- package/dist/src/commands/pool.js +139 -0
- package/dist/src/commands/resume.d.ts +1 -0
- package/dist/src/commands/resume.js +88 -0
- package/dist/src/commands/spawn.d.ts +1 -0
- package/dist/src/commands/spawn.js +195 -0
- package/dist/src/commands/status.d.ts +21 -0
- package/dist/src/commands/status.js +122 -0
- package/dist/src/commands/sync.d.ts +47 -0
- package/dist/src/commands/sync.js +341 -0
- package/dist/src/commands/teardown.d.ts +1 -0
- package/dist/src/commands/teardown.js +87 -0
- package/dist/src/commands/tuition.d.ts +21 -0
- package/dist/src/commands/tuition.js +218 -0
- package/dist/src/commands/validate.d.ts +78 -0
- package/dist/src/commands/validate.js +430 -0
- package/dist/src/commands/watch.d.ts +1 -0
- package/dist/src/commands/watch.js +172 -0
- package/dist/src/cutover.d.ts +584 -0
- package/dist/src/cutover.js +1444 -0
- package/dist/src/exclusion.d.ts +389 -0
- package/dist/src/exclusion.js +843 -0
- package/dist/src/exec/env.d.ts +278 -0
- package/dist/src/exec/env.js +334 -0
- package/dist/src/fleet.d.ts +223 -0
- package/dist/src/fleet.js +298 -0
- package/dist/src/gates/adapters/http-json.d.ts +5 -0
- package/dist/src/gates/adapters/http-json.js +283 -0
- package/dist/src/gates/adapters/migrations-command.d.ts +1 -0
- package/dist/src/gates/adapters/migrations-command.js +373 -0
- package/dist/src/gates/citations.d.ts +408 -0
- package/dist/src/gates/citations.js +1169 -0
- package/dist/src/gates/coverage.d.ts +375 -0
- package/dist/src/gates/coverage.js +836 -0
- package/dist/src/gates/credentials.d.ts +233 -0
- package/dist/src/gates/credentials.js +752 -0
- package/dist/src/gates/deploy.d.ts +1 -0
- package/dist/src/gates/deploy.js +33 -0
- package/dist/src/gates/gate-classes.d.ts +56 -0
- package/dist/src/gates/gate-classes.js +633 -0
- package/dist/src/gates/manifest.d.ts +99 -0
- package/dist/src/gates/manifest.js +208 -0
- package/dist/src/gates/merge-preconditions.d.ts +319 -0
- package/dist/src/gates/merge-preconditions.js +932 -0
- package/dist/src/gates/migrations.d.ts +1 -0
- package/dist/src/gates/migrations.js +36 -0
- package/dist/src/gates/pin.d.ts +114 -0
- package/dist/src/gates/pin.js +154 -0
- package/dist/src/gates/red-witness.d.ts +22 -0
- package/dist/src/gates/red-witness.js +482 -0
- package/dist/src/gates/release.d.ts +283 -0
- package/dist/src/gates/release.js +820 -0
- package/dist/src/gates/result.d.ts +116 -0
- package/dist/src/gates/result.js +91 -0
- package/dist/src/gates/run.d.ts +614 -0
- package/dist/src/gates/run.js +1614 -0
- package/dist/src/gates/schemas/citation-config.schema.json +59 -0
- package/dist/src/gates/schemas/coverage-config.schema.json +77 -0
- package/dist/src/gates/schemas/gate-manifest.schema.json +125 -0
- package/dist/src/gates/schemas/gate-result.schema.json +160 -0
- package/dist/src/gates/schemas/phase-declaration.schema.json +87 -0
- package/dist/src/gates/schemas/release-record.schema.json +119 -0
- package/dist/src/gates/schemas/verifier-config.schema.json +101 -0
- package/dist/src/gates/schemas/witness-spec.schema.json +110 -0
- package/dist/src/gates/scope.d.ts +131 -0
- package/dist/src/gates/scope.js +1018 -0
- package/dist/src/gates/suite.d.ts +217 -0
- package/dist/src/gates/suite.js +968 -0
- package/dist/src/gates/validate.d.ts +121 -0
- package/dist/src/gates/validate.js +414 -0
- package/dist/src/hooks.d.ts +84 -0
- package/dist/src/hooks.js +125 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.js +30 -0
- package/dist/src/liveness.d.ts +321 -0
- package/dist/src/liveness.js +396 -0
- package/dist/src/lock.d.ts +256 -0
- package/dist/src/lock.js +792 -0
- package/dist/src/model-resolution.d.ts +159 -0
- package/dist/src/model-resolution.js +307 -0
- package/dist/src/modes.d.ts +149 -0
- package/dist/src/modes.js +258 -0
- package/dist/src/path-identity.d.ts +34 -0
- package/dist/src/path-identity.js +48 -0
- package/dist/src/plan.d.ts +73 -0
- package/dist/src/plan.js +153 -0
- package/dist/src/pool.d.ts +326 -0
- package/dist/src/pool.js +988 -0
- package/dist/src/roles.d.ts +461 -0
- package/dist/src/roles.js +776 -0
- package/dist/src/spawn.d.ts +482 -0
- package/dist/src/spawn.js +1003 -0
- package/dist/src/status.d.ts +116 -0
- package/dist/src/status.js +148 -0
- package/dist/src/task.d.ts +504 -0
- package/dist/src/task.js +307 -0
- package/dist/src/teardown.d.ts +39 -0
- package/dist/src/teardown.js +422 -0
- package/dist/src/tuition.d.ts +159 -0
- package/dist/src/tuition.js +311 -0
- package/dist/src/validate.d.ts +263 -0
- package/dist/src/validate.js +742 -0
- package/dist/src/version.d.ts +3 -0
- package/dist/src/version.js +38 -0
- package/dist/src/watcher.d.ts +275 -0
- package/dist/src/watcher.js +849 -0
- package/dist/src/witness/run.d.ts +299 -0
- package/dist/src/witness/run.js +1373 -0
- package/dist/src/witness/spec.d.ts +270 -0
- package/dist/src/witness/spec.js +475 -0
- package/dist/tsconfig.src.tsbuildinfo +1 -0
- package/gate-registry.yaml +526 -0
- package/gates.manifest.json +257 -0
- package/package.json +72 -3
- package/role-model-config.yaml +88 -0
- package/roles/README.md +128 -0
- package/roles/_shared-dispatch-contract.md +87 -0
- package/roles/adversarial-plan-reviewer.md +80 -0
- package/roles/clean-room-reviewer.md +140 -0
- package/roles/implementer.md +463 -0
- package/roles/investigator.md +138 -0
- package/roles/plan-writer.md +95 -0
- package/schemas/README.md +82 -0
- package/schemas/assurance-modes.schema.json +264 -0
- package/schemas/charter.schema.json +185 -0
- package/schemas/checklist.schema.json +114 -0
- package/schemas/cutover-state.schema.json +64 -0
- package/schemas/decision-record.schema.json +88 -0
- package/schemas/executor-record.schema.json +36 -0
- package/schemas/final-report.schema.json +90 -0
- package/schemas/finding.schema.json +106 -0
- package/schemas/gate-registry.schema.json +260 -0
- package/schemas/mechanism-index.schema.json +94 -0
- package/schemas/model-resolution.schema.json +362 -0
- package/schemas/plan.schema.json +300 -0
- package/schemas/report.schema.json +579 -0
- package/schemas/role-brief.schema.json +105 -0
- package/schemas/role-model-config.schema.json +90 -0
- package/schemas/status-line.schema.json +40 -0
- package/schemas/tuition.schema.json +191 -0
- package/schemas/verdict.schema.json +295 -0
- package/schemas/work-history.schema.json +183 -0
- package/schemas/write-bypass.schema.json +69 -0
- package/templates/charter.example.yaml +54 -0
- package/templates/decision-record.example.yaml +27 -0
- package/templates/final-report.example.yaml +80 -0
- package/templates/plan.example.yaml +87 -0
- package/templates/report.example.yaml +236 -0
- package/templates/warnings.md +74 -0
- package/templates/work-history.example.yaml +185 -0
- package/tuition/README.md +76 -0
- package/tuition/T-001.yaml +48 -0
- package/tuition/T-002.yaml +51 -0
- package/tuition/T-003.yaml +100 -0
- package/tuition/T-004.yaml +52 -0
- package/tuition/T-005.yaml +72 -0
- package/tuition/T-006.yaml +81 -0
- package/tuition/T-007.yaml +56 -0
- package/tuition/T-008.yaml +111 -0
- package/tuition/T-009.yaml +50 -0
- package/tuition/T-015.yaml +36 -0
- package/tuition/T-016.yaml +36 -0
- package/tuition/T-017.yaml +46 -0
- package/tuition/T-018.yaml +84 -0
- package/tuition/T-021.yaml +40 -0
- package/tuition/T-022.yaml +36 -0
- package/tuition/mechanism-index.yaml +256 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE MINIMAL IN-REPO SCHEMA VALIDATOR (kernel plan M2, M2-P1 step 4;
|
|
3
|
+
* M2-D-04; DR-0013 clause 6).
|
|
4
|
+
*
|
|
5
|
+
* M1 shipped zero runtime dependencies. DR-0013 decided the library
|
|
6
|
+
* question for M3 (Ajv 8.20.0 exact, Draft 2020-12) and made THIS module the
|
|
7
|
+
* seam: M3-P1 retires the engine below and keeps the boundary. That promise
|
|
8
|
+
* is only keepable if two things are true here, and both are enforced by
|
|
9
|
+
* this module's tests rather than asserted in prose:
|
|
10
|
+
*
|
|
11
|
+
* 1. THE DIAGNOSTIC CONTRACT IS `INVALID <json-pointer> <message>`, one
|
|
12
|
+
* line per failure, in a deterministic order. That string is this
|
|
13
|
+
* module's public output. Everything else about how a failure was
|
|
14
|
+
* found is an implementation detail and no test may assert on it.
|
|
15
|
+
* 2. THE MESSAGE TEXTS BELOW ARE THE CONTRACT, not this engine's wording.
|
|
16
|
+
* They are listed in `DIAGNOSTIC_MESSAGES` so a future engine has one
|
|
17
|
+
* table to map onto instead of a search through call sites.
|
|
18
|
+
*
|
|
19
|
+
* Without those two, DR-0013's "all existing M2 validation tests are re-run
|
|
20
|
+
* against it" would be a rewrite of the tests rather than an engine swap,
|
|
21
|
+
* and "the module boundary is preserved" would be false at the moment it was
|
|
22
|
+
* acted on.
|
|
23
|
+
*
|
|
24
|
+
* DETERMINISTIC ORDERING, stated so a replacement engine can reproduce it:
|
|
25
|
+
* every diagnostic is collected during a traversal that visits object
|
|
26
|
+
* properties in sorted key order and array items in index order, and the
|
|
27
|
+
* collected list is then sorted by (pointer, message) with ASCII
|
|
28
|
+
* lexicographic comparison. The final sort is what makes the order a
|
|
29
|
+
* property of the CONTRACT rather than of the traversal, so an engine that
|
|
30
|
+
* walks differently still emits the same lines in the same order.
|
|
31
|
+
*
|
|
32
|
+
* THE CLOSED KEYWORD SET, AND WHY IT IS LOUD (M2-D-04). Ten validation
|
|
33
|
+
* keywords plus local `$ref`. A schema document containing any other
|
|
34
|
+
* keyword is a LOAD ERROR naming the keyword and its location. It is never
|
|
35
|
+
* ignored, because a validator that silently skips `oneOf` reports a
|
|
36
|
+
* document valid while never having checked the constraint that mattered,
|
|
37
|
+
* which is the same shape as "green by omission" one layer down. The set is
|
|
38
|
+
* deliberately NOT grown to anticipate M3's five artifact schemas; DR-0013
|
|
39
|
+
* decided that question against extension.
|
|
40
|
+
*/
|
|
41
|
+
/** The ten validation keywords plus local `$ref`. Nothing else validates. */
|
|
42
|
+
export declare const VALIDATION_KEYWORDS: readonly string[];
|
|
43
|
+
/**
|
|
44
|
+
* Keys that carry no constraint and are permitted for documentation and for
|
|
45
|
+
* holding subschemas. They are listed separately from the validation
|
|
46
|
+
* keywords on purpose: a reader must be able to see that permitting
|
|
47
|
+
* `description` is a decision about annotations and not a hole in the closed
|
|
48
|
+
* set.
|
|
49
|
+
*/
|
|
50
|
+
export declare const ANNOTATION_KEYS: readonly string[];
|
|
51
|
+
export interface Diagnostic {
|
|
52
|
+
pointer: string;
|
|
53
|
+
message: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `INVALID <json-pointer> <message>`. The module's public contract.
|
|
57
|
+
*
|
|
58
|
+
* Pointers are JSON Pointers in RFC 6901 URI-fragment form, so the document
|
|
59
|
+
* root is `#` and a nested location is `#/gates/0/id`. The fragment form is
|
|
60
|
+
* chosen over the bare form for one mechanical reason: the bare pointer to
|
|
61
|
+
* the root is the EMPTY STRING, which would render this line with two
|
|
62
|
+
* consecutive spaces and make the three fields ambiguous to split. A
|
|
63
|
+
* diagnostic is reported at the pointer of the OFFENDING LOCATION, not of
|
|
64
|
+
* its parent, so a missing required property points at the property that
|
|
65
|
+
* should have been there.
|
|
66
|
+
*/
|
|
67
|
+
export declare function formatDiagnostic(diagnostic: Diagnostic): string;
|
|
68
|
+
export declare function formatDiagnostics(diagnostics: Diagnostic[]): string[];
|
|
69
|
+
/**
|
|
70
|
+
* THE MESSAGE CONTRACT. One entry per way a value can fail. A replacement
|
|
71
|
+
* engine maps its own errors onto these; a test asserts on these and never
|
|
72
|
+
* on anything else.
|
|
73
|
+
*/
|
|
74
|
+
export declare const DIAGNOSTIC_MESSAGES: {
|
|
75
|
+
type: (expected: string, observed: string) => string;
|
|
76
|
+
required: (name: string) => string;
|
|
77
|
+
additionalProperties: (name: string) => string;
|
|
78
|
+
enum: (value: string, permitted: string) => string;
|
|
79
|
+
const: (value: string, required: string) => string;
|
|
80
|
+
minimum: (value: string, minimum: string) => string;
|
|
81
|
+
minItems: (count: string, minimum: string) => string;
|
|
82
|
+
pattern: (value: string, pattern: string) => string;
|
|
83
|
+
/**
|
|
84
|
+
* Authored by src/gates/manifest.ts, which cannot express "keyed by name"
|
|
85
|
+
* in the closed keyword set. It lives in THIS table anyway (CR-811): the
|
|
86
|
+
* table's stated purpose is to give a future engine one place to map onto
|
|
87
|
+
* instead of a search through call sites, and a message that sits outside
|
|
88
|
+
* it defeats that purpose whatever module emits it.
|
|
89
|
+
*/
|
|
90
|
+
duplicateId: (id: string) => string;
|
|
91
|
+
/** A $ref chain that returns to itself without consuming an instance. */
|
|
92
|
+
cyclicRef: (reference: string) => string;
|
|
93
|
+
unresolvedRef: (reference: string) => string;
|
|
94
|
+
};
|
|
95
|
+
export type SchemaDocument = Record<string, unknown>;
|
|
96
|
+
export type SchemaLoad = {
|
|
97
|
+
ok: true;
|
|
98
|
+
schema: SchemaDocument;
|
|
99
|
+
} | {
|
|
100
|
+
ok: false;
|
|
101
|
+
reason: string;
|
|
102
|
+
};
|
|
103
|
+
/** The document root, in URI-fragment form. */
|
|
104
|
+
export declare const ROOT_POINTER = "#";
|
|
105
|
+
/** The JSON type name of a value, as the `type` keyword uses it. */
|
|
106
|
+
export declare function jsonTypeOf(value: unknown): string;
|
|
107
|
+
/**
|
|
108
|
+
* Load a schema document, refusing loudly on any keyword outside the closed
|
|
109
|
+
* set. `name` appears in the reason so a caller with several documents knows
|
|
110
|
+
* which one failed.
|
|
111
|
+
*/
|
|
112
|
+
export declare function loadSchema(document: unknown, name: string): SchemaLoad;
|
|
113
|
+
/**
|
|
114
|
+
* Validate an instance against a loaded schema. The returned list is sorted
|
|
115
|
+
* by (pointer, message), which is the deterministic order the contract
|
|
116
|
+
* promises and which is now produced by `sortDiagnostics` in the shared
|
|
117
|
+
* engine so the two modules cannot drift on it.
|
|
118
|
+
*/
|
|
119
|
+
export declare function validate(schema: SchemaDocument, instance: unknown): Diagnostic[];
|
|
120
|
+
/** Validate and format in one step: the shape most callers want. */
|
|
121
|
+
export declare function validateToLines(schema: SchemaDocument, instance: unknown): string[];
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE MINIMAL IN-REPO SCHEMA VALIDATOR (kernel plan M2, M2-P1 step 4;
|
|
3
|
+
* M2-D-04; DR-0013 clause 6).
|
|
4
|
+
*
|
|
5
|
+
* M1 shipped zero runtime dependencies. DR-0013 decided the library
|
|
6
|
+
* question for M3 (Ajv 8.20.0 exact, Draft 2020-12) and made THIS module the
|
|
7
|
+
* seam: M3-P1 retires the engine below and keeps the boundary. That promise
|
|
8
|
+
* is only keepable if two things are true here, and both are enforced by
|
|
9
|
+
* this module's tests rather than asserted in prose:
|
|
10
|
+
*
|
|
11
|
+
* 1. THE DIAGNOSTIC CONTRACT IS `INVALID <json-pointer> <message>`, one
|
|
12
|
+
* line per failure, in a deterministic order. That string is this
|
|
13
|
+
* module's public output. Everything else about how a failure was
|
|
14
|
+
* found is an implementation detail and no test may assert on it.
|
|
15
|
+
* 2. THE MESSAGE TEXTS BELOW ARE THE CONTRACT, not this engine's wording.
|
|
16
|
+
* They are listed in `DIAGNOSTIC_MESSAGES` so a future engine has one
|
|
17
|
+
* table to map onto instead of a search through call sites.
|
|
18
|
+
*
|
|
19
|
+
* Without those two, DR-0013's "all existing M2 validation tests are re-run
|
|
20
|
+
* against it" would be a rewrite of the tests rather than an engine swap,
|
|
21
|
+
* and "the module boundary is preserved" would be false at the moment it was
|
|
22
|
+
* acted on.
|
|
23
|
+
*
|
|
24
|
+
* DETERMINISTIC ORDERING, stated so a replacement engine can reproduce it:
|
|
25
|
+
* every diagnostic is collected during a traversal that visits object
|
|
26
|
+
* properties in sorted key order and array items in index order, and the
|
|
27
|
+
* collected list is then sorted by (pointer, message) with ASCII
|
|
28
|
+
* lexicographic comparison. The final sort is what makes the order a
|
|
29
|
+
* property of the CONTRACT rather than of the traversal, so an engine that
|
|
30
|
+
* walks differently still emits the same lines in the same order.
|
|
31
|
+
*
|
|
32
|
+
* THE CLOSED KEYWORD SET, AND WHY IT IS LOUD (M2-D-04). Ten validation
|
|
33
|
+
* keywords plus local `$ref`. A schema document containing any other
|
|
34
|
+
* keyword is a LOAD ERROR naming the keyword and its location. It is never
|
|
35
|
+
* ignored, because a validator that silently skips `oneOf` reports a
|
|
36
|
+
* document valid while never having checked the constraint that mattered,
|
|
37
|
+
* which is the same shape as "green by omission" one layer down. The set is
|
|
38
|
+
* deliberately NOT grown to anticipate M3's five artifact schemas; DR-0013
|
|
39
|
+
* decided that question against extension.
|
|
40
|
+
*/
|
|
41
|
+
import { sortDiagnostics, validateInstance, } from "../validate.js";
|
|
42
|
+
/** The ten validation keywords plus local `$ref`. Nothing else validates. */
|
|
43
|
+
export const VALIDATION_KEYWORDS = [
|
|
44
|
+
"type",
|
|
45
|
+
"required",
|
|
46
|
+
"properties",
|
|
47
|
+
"additionalProperties",
|
|
48
|
+
"enum",
|
|
49
|
+
"items",
|
|
50
|
+
"minimum",
|
|
51
|
+
"minItems",
|
|
52
|
+
"pattern",
|
|
53
|
+
"const",
|
|
54
|
+
"$ref",
|
|
55
|
+
];
|
|
56
|
+
/**
|
|
57
|
+
* Keys that carry no constraint and are permitted for documentation and for
|
|
58
|
+
* holding subschemas. They are listed separately from the validation
|
|
59
|
+
* keywords on purpose: a reader must be able to see that permitting
|
|
60
|
+
* `description` is a decision about annotations and not a hole in the closed
|
|
61
|
+
* set.
|
|
62
|
+
*/
|
|
63
|
+
export const ANNOTATION_KEYS = [
|
|
64
|
+
"$schema",
|
|
65
|
+
"$id",
|
|
66
|
+
"title",
|
|
67
|
+
"description",
|
|
68
|
+
"$defs",
|
|
69
|
+
];
|
|
70
|
+
/**
|
|
71
|
+
* `INVALID <json-pointer> <message>`. The module's public contract.
|
|
72
|
+
*
|
|
73
|
+
* Pointers are JSON Pointers in RFC 6901 URI-fragment form, so the document
|
|
74
|
+
* root is `#` and a nested location is `#/gates/0/id`. The fragment form is
|
|
75
|
+
* chosen over the bare form for one mechanical reason: the bare pointer to
|
|
76
|
+
* the root is the EMPTY STRING, which would render this line with two
|
|
77
|
+
* consecutive spaces and make the three fields ambiguous to split. A
|
|
78
|
+
* diagnostic is reported at the pointer of the OFFENDING LOCATION, not of
|
|
79
|
+
* its parent, so a missing required property points at the property that
|
|
80
|
+
* should have been there.
|
|
81
|
+
*/
|
|
82
|
+
export function formatDiagnostic(diagnostic) {
|
|
83
|
+
return `INVALID ${diagnostic.pointer} ${diagnostic.message}`;
|
|
84
|
+
}
|
|
85
|
+
export function formatDiagnostics(diagnostics) {
|
|
86
|
+
return diagnostics.map(formatDiagnostic);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* THE MESSAGE CONTRACT. One entry per way a value can fail. A replacement
|
|
90
|
+
* engine maps its own errors onto these; a test asserts on these and never
|
|
91
|
+
* on anything else.
|
|
92
|
+
*/
|
|
93
|
+
export const DIAGNOSTIC_MESSAGES = {
|
|
94
|
+
type: (expected, observed) => `expected type ${expected} but found ${observed}`,
|
|
95
|
+
required: (name) => `required property ${name} is missing`,
|
|
96
|
+
additionalProperties: (name) => `property ${name} is not permitted here`,
|
|
97
|
+
enum: (value, permitted) => `value ${value} is not one of the permitted values ${permitted}`,
|
|
98
|
+
const: (value, required) => `value ${value} does not equal the required constant ${required}`,
|
|
99
|
+
minimum: (value, minimum) => `value ${value} is below the minimum ${minimum}`,
|
|
100
|
+
minItems: (count, minimum) => `array has ${count} items, fewer than the required minimum ${minimum}`,
|
|
101
|
+
pattern: (value, pattern) => `value ${value} does not match the required pattern ${pattern}`,
|
|
102
|
+
/**
|
|
103
|
+
* Authored by src/gates/manifest.ts, which cannot express "keyed by name"
|
|
104
|
+
* in the closed keyword set. It lives in THIS table anyway (CR-811): the
|
|
105
|
+
* table's stated purpose is to give a future engine one place to map onto
|
|
106
|
+
* instead of a search through call sites, and a message that sits outside
|
|
107
|
+
* it defeats that purpose whatever module emits it.
|
|
108
|
+
*/
|
|
109
|
+
duplicateId: (id) => `gate id ${id} is declared more than once`,
|
|
110
|
+
/** A $ref chain that returns to itself without consuming an instance. */
|
|
111
|
+
cyclicRef: (reference) => `schema reference ${reference} is cyclic`,
|
|
112
|
+
unresolvedRef: (reference) => `schema reference ${reference} does not resolve`,
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Own-property test. `properties["__proto__"]` resolves through the prototype
|
|
116
|
+
* chain to `Object.prototype`, which is an object, so a naive lookup treats
|
|
117
|
+
* `__proto__` as a DECLARED property and lets it through
|
|
118
|
+
* `additionalProperties: false` (CR-808). Ajv rejects it, so this was also a
|
|
119
|
+
* seam divergence.
|
|
120
|
+
*/
|
|
121
|
+
function ownProperty(container, name) {
|
|
122
|
+
return Object.prototype.hasOwnProperty.call(container, name)
|
|
123
|
+
? container[name]
|
|
124
|
+
: undefined;
|
|
125
|
+
}
|
|
126
|
+
function pointerSegment(segment) {
|
|
127
|
+
return segment.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
128
|
+
}
|
|
129
|
+
function childPointer(pointer, segment) {
|
|
130
|
+
return `${pointer}/${pointerSegment(segment)}`;
|
|
131
|
+
}
|
|
132
|
+
/** The document root, in URI-fragment form. */
|
|
133
|
+
export const ROOT_POINTER = "#";
|
|
134
|
+
function isPlainObject(value) {
|
|
135
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
136
|
+
}
|
|
137
|
+
/** The JSON type name of a value, as the `type` keyword uses it. */
|
|
138
|
+
export function jsonTypeOf(value) {
|
|
139
|
+
if (value === null) {
|
|
140
|
+
return "null";
|
|
141
|
+
}
|
|
142
|
+
if (Array.isArray(value)) {
|
|
143
|
+
return "array";
|
|
144
|
+
}
|
|
145
|
+
const primitive = typeof value;
|
|
146
|
+
if (primitive === "number") {
|
|
147
|
+
return Number.isInteger(value) ? "integer" : "number";
|
|
148
|
+
}
|
|
149
|
+
if (primitive === "boolean") {
|
|
150
|
+
return "boolean";
|
|
151
|
+
}
|
|
152
|
+
if (primitive === "string") {
|
|
153
|
+
return "string";
|
|
154
|
+
}
|
|
155
|
+
return "object";
|
|
156
|
+
}
|
|
157
|
+
const PERMITTED_TYPES = [
|
|
158
|
+
"object",
|
|
159
|
+
"array",
|
|
160
|
+
"string",
|
|
161
|
+
"number",
|
|
162
|
+
"integer",
|
|
163
|
+
"boolean",
|
|
164
|
+
"null",
|
|
165
|
+
];
|
|
166
|
+
/**
|
|
167
|
+
* Walk a schema document and refuse anything outside the closed set. The
|
|
168
|
+
* walk knows WHERE subschemas live (`properties/*`, `items`, `$defs/*` and
|
|
169
|
+
* the root), so a property literally named "oneOf" inside `properties` is a
|
|
170
|
+
* property name and not a keyword, and is not confused for one.
|
|
171
|
+
*/
|
|
172
|
+
function checkSchemaNode(node, pointer, problems) {
|
|
173
|
+
if (!isPlainObject(node)) {
|
|
174
|
+
problems.push(`${pointer} is not a schema object`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
for (const key of Object.keys(node).sort()) {
|
|
178
|
+
if (VALIDATION_KEYWORDS.includes(key) || ANNOTATION_KEYS.includes(key)) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
problems.push(`unsupported schema keyword ${key} at ${pointer}`);
|
|
182
|
+
}
|
|
183
|
+
const type = node["type"];
|
|
184
|
+
if (type !== undefined) {
|
|
185
|
+
if (typeof type !== "string" || !PERMITTED_TYPES.includes(type)) {
|
|
186
|
+
problems.push(`unsupported type value at ${pointer}: this validator accepts a single type name from ${PERMITTED_TYPES.join(", ")}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const additional = node["additionalProperties"];
|
|
190
|
+
if (additional !== undefined && typeof additional !== "boolean") {
|
|
191
|
+
problems.push(`unsupported additionalProperties at ${pointer}: this validator accepts a boolean only`);
|
|
192
|
+
}
|
|
193
|
+
const required = node["required"];
|
|
194
|
+
if (required !== undefined && !Array.isArray(required)) {
|
|
195
|
+
problems.push(`required at ${pointer} is not an array`);
|
|
196
|
+
}
|
|
197
|
+
const enumeration = node["enum"];
|
|
198
|
+
if (enumeration !== undefined && !Array.isArray(enumeration)) {
|
|
199
|
+
problems.push(`enum at ${pointer} is not an array`);
|
|
200
|
+
}
|
|
201
|
+
const reference = node["$ref"];
|
|
202
|
+
if (reference !== undefined && typeof reference !== "string") {
|
|
203
|
+
problems.push(`$ref at ${pointer} is not a string`);
|
|
204
|
+
}
|
|
205
|
+
if (reference !== undefined) {
|
|
206
|
+
// CR-802. JSON Schema 2020-12 APPLIES keywords sitting beside `$ref`,
|
|
207
|
+
// and Ajv does too. This engine followed the reference and returned,
|
|
208
|
+
// dropping every sibling without a word: a KNOWN keyword in a position
|
|
209
|
+
// the validator silently ignores, which is the harder half of the
|
|
210
|
+
// attack the closed keyword set exists to stop, and a verdict change at
|
|
211
|
+
// the exact seam DR-0013 clause 6 promises M3-P1 can swap across.
|
|
212
|
+
//
|
|
213
|
+
// Refusing at load rather than implementing sibling application is the
|
|
214
|
+
// choice this module's philosophy already made everywhere else: a
|
|
215
|
+
// schema this engine cannot evaluate the way the specification says is
|
|
216
|
+
// rejected, never partially honoured. A schema that never uses the
|
|
217
|
+
// construct behaves identically under both engines.
|
|
218
|
+
const siblings = Object.keys(node)
|
|
219
|
+
.filter((key) => key !== "$ref" && VALIDATION_KEYWORDS.includes(key))
|
|
220
|
+
.sort();
|
|
221
|
+
if (siblings.length > 0) {
|
|
222
|
+
problems.push(`$ref at ${pointer} has sibling keyword(s) ${siblings.join(", ")}: ` +
|
|
223
|
+
"this validator does not apply keywords beside a $ref, and " +
|
|
224
|
+
"silently ignoring them would validate less than the schema says");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const properties = node["properties"];
|
|
228
|
+
if (properties !== undefined) {
|
|
229
|
+
if (!isPlainObject(properties)) {
|
|
230
|
+
problems.push(`properties at ${pointer} is not an object`);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
for (const name of Object.keys(properties).sort()) {
|
|
234
|
+
checkSchemaNode(ownProperty(properties, name), `${childPointer(pointer, "properties")}/${pointerSegment(name)}`, problems);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const defs = node["$defs"];
|
|
239
|
+
if (defs !== undefined) {
|
|
240
|
+
if (!isPlainObject(defs)) {
|
|
241
|
+
problems.push(`$defs at ${pointer} is not an object`);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
for (const name of Object.keys(defs).sort()) {
|
|
245
|
+
checkSchemaNode(ownProperty(defs, name), `${childPointer(pointer, "$defs")}/${pointerSegment(name)}`, problems);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const patternSource = node["pattern"];
|
|
250
|
+
if (patternSource !== undefined) {
|
|
251
|
+
if (typeof patternSource !== "string") {
|
|
252
|
+
problems.push(`pattern at ${pointer} is not a string`);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
// Compiled HERE so an unusable pattern is a load failure with a
|
|
256
|
+
// reason, not a throw escaping mid-validation. CR-801's derivation
|
|
257
|
+
// named this exact asymmetry: `new RegExp` on a CALLER-supplied
|
|
258
|
+
// pattern was guarded and `new RegExp` on a SCHEMA-supplied one was
|
|
259
|
+
// not, same call, same failure, one guarded.
|
|
260
|
+
try {
|
|
261
|
+
new RegExp(patternSource);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
problems.push(`pattern at ${pointer} is not a valid expression: ${error.message}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const items = node["items"];
|
|
269
|
+
if (items !== undefined) {
|
|
270
|
+
checkSchemaNode(items, childPointer(pointer, "items"), problems);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Load a schema document, refusing loudly on any keyword outside the closed
|
|
275
|
+
* set. `name` appears in the reason so a caller with several documents knows
|
|
276
|
+
* which one failed.
|
|
277
|
+
*/
|
|
278
|
+
export function loadSchema(document, name) {
|
|
279
|
+
const problems = [];
|
|
280
|
+
checkSchemaNode(document, ROOT_POINTER, problems);
|
|
281
|
+
if (problems.length > 0) {
|
|
282
|
+
return {
|
|
283
|
+
ok: false,
|
|
284
|
+
reason: `${name}: ${problems.sort().join("; ")}`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return { ok: true, schema: document };
|
|
288
|
+
}
|
|
289
|
+
function resolveRef(root, reference) {
|
|
290
|
+
// `#` is the whole document, which is how a recursive schema is normally
|
|
291
|
+
// written. It was unhandled until a control in test/gates.test.ts exercised
|
|
292
|
+
// a legitimate recursive schema and got "does not resolve"; the cycle fix
|
|
293
|
+
// that motivated the control would otherwise have shipped beside a gap
|
|
294
|
+
// that made every recursive schema unusable.
|
|
295
|
+
if (reference === "#") {
|
|
296
|
+
return root;
|
|
297
|
+
}
|
|
298
|
+
if (!reference.startsWith("#/")) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
let node = root;
|
|
302
|
+
for (const raw of reference.slice(2).split("/")) {
|
|
303
|
+
const segment = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
304
|
+
if (!isPlainObject(node)) {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
node = ownProperty(node, segment);
|
|
308
|
+
}
|
|
309
|
+
return isPlainObject(node) ? node : undefined;
|
|
310
|
+
}
|
|
311
|
+
/* ------------------------------------------------------------------ */
|
|
312
|
+
/* THE ENGINE, RETIRED (DR-0013 clause 6, M3-P1 step 8). */
|
|
313
|
+
/* ------------------------------------------------------------------ */
|
|
314
|
+
/**
|
|
315
|
+
* WHAT WAS RETIRED AND WHAT WAS KEPT, stated because the distinction is the
|
|
316
|
+
* whole of DR-0013 clause 6.
|
|
317
|
+
*
|
|
318
|
+
* RETIRED: keyword SEMANTICS. `type`, `required`, `properties`,
|
|
319
|
+
* `additionalProperties`, `enum`, `items`, `minimum`, `minItems`, `pattern`
|
|
320
|
+
* and `const` are no longer evaluated here. `validate` hands the schema and
|
|
321
|
+
* the instance to `src/validate.ts`, which is Ajv 8.20.0 under the policies
|
|
322
|
+
* DR-0013 clause 4 fixed. Two engines with potentially different semantics
|
|
323
|
+
* are not maintained.
|
|
324
|
+
*
|
|
325
|
+
* KEPT, and each for a reason DR-0013 names:
|
|
326
|
+
*
|
|
327
|
+
* 1. THE MODULE BOUNDARY. `loadSchema`, `validate`, `validateToLines`,
|
|
328
|
+
* `formatDiagnostic`, `formatDiagnostics` and `DIAGNOSTIC_MESSAGES` are
|
|
329
|
+
* unchanged in name, signature and meaning, so every M2 caller and every
|
|
330
|
+
* M2 test is untouched.
|
|
331
|
+
* 2. THE CLOSED KEYWORD SET, as a POLICY LINTER. `loadSchema` still refuses
|
|
332
|
+
* a gate schema containing a keyword outside M2-D-04's set. DR-0013
|
|
333
|
+
* clause 7 says this explicitly: prohibiting otherwise-valid but
|
|
334
|
+
* unapproved keywords "is a small schema-aware POLICY LINTER, never a
|
|
335
|
+
* reimplementation of keyword semantics". Under Ajv `oneOf` compiles
|
|
336
|
+
* fine, so without this linter M2's gate schemas would silently acquire
|
|
337
|
+
* a vocabulary nobody approved.
|
|
338
|
+
* 3. THE REFERENCE POLICY. Measured 2026-08-08 with the pinned Ajv:
|
|
339
|
+
* compiling `{type:"object",properties:{x:{$ref:"#/$defs/a"}},
|
|
340
|
+
* $defs:{a:{$ref:"#/$defs/a"}}}` raises
|
|
341
|
+
* `RangeError: Maximum call stack size exceeded`. That is CR-807's
|
|
342
|
+
* failure wearing a different engine's clothes, so the cycle and
|
|
343
|
+
* unresolved-reference analysis stays HERE, in front of the engine,
|
|
344
|
+
* where it produces a diagnostic instead of a stack overflow. It
|
|
345
|
+
* evaluates no other keyword.
|
|
346
|
+
*/
|
|
347
|
+
/**
|
|
348
|
+
* Reference-only joint walk. Follows `$ref` and descends `properties` and
|
|
349
|
+
* `items`, and does nothing else. `refChain` holds the references followed
|
|
350
|
+
* since the last INSTANCE node was consumed, so a legitimate recursive schema
|
|
351
|
+
* (which always consumes an instance node between two follows) is not
|
|
352
|
+
* mistaken for a cycle. This is the same rule the retired engine used and the
|
|
353
|
+
* same diagnostics, because both are contract.
|
|
354
|
+
*/
|
|
355
|
+
function collectReferenceDiagnostics(root, schema, instance, pointer, into, refChain, seenNodes) {
|
|
356
|
+
const reference = schema["$ref"];
|
|
357
|
+
if (typeof reference === "string") {
|
|
358
|
+
if (refChain.includes(reference)) {
|
|
359
|
+
into.push({ pointer, message: DIAGNOSTIC_MESSAGES.cyclicRef(reference) });
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const target = resolveRef(root, reference);
|
|
363
|
+
if (target === undefined) {
|
|
364
|
+
into.push({
|
|
365
|
+
pointer,
|
|
366
|
+
message: DIAGNOSTIC_MESSAGES.unresolvedRef(reference),
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
collectReferenceDiagnostics(root, target, instance, pointer, into, [...refChain, reference], seenNodes);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (Array.isArray(instance)) {
|
|
374
|
+
const items = schema["items"];
|
|
375
|
+
if (isPlainObject(items)) {
|
|
376
|
+
for (let index = 0; index < instance.length; index += 1) {
|
|
377
|
+
collectReferenceDiagnostics(root, items, instance[index], `${pointer}/${index}`, into, [], seenNodes);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (isPlainObject(instance)) {
|
|
383
|
+
const properties = isPlainObject(schema["properties"])
|
|
384
|
+
? schema["properties"]
|
|
385
|
+
: {};
|
|
386
|
+
for (const name of Object.keys(instance).sort()) {
|
|
387
|
+
const subschema = ownProperty(properties, name);
|
|
388
|
+
if (isPlainObject(subschema)) {
|
|
389
|
+
collectReferenceDiagnostics(root, subschema, ownProperty(instance, name), childPointer(pointer, name), into, [], seenNodes);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Validate an instance against a loaded schema. The returned list is sorted
|
|
396
|
+
* by (pointer, message), which is the deterministic order the contract
|
|
397
|
+
* promises and which is now produced by `sortDiagnostics` in the shared
|
|
398
|
+
* engine so the two modules cannot drift on it.
|
|
399
|
+
*/
|
|
400
|
+
export function validate(schema, instance) {
|
|
401
|
+
const referenceProblems = [];
|
|
402
|
+
collectReferenceDiagnostics(schema, schema, instance, ROOT_POINTER, referenceProblems, [], new Set());
|
|
403
|
+
if (referenceProblems.length > 0) {
|
|
404
|
+
// A schema whose references do not resolve, or loop, cannot be compiled
|
|
405
|
+
// at all: the engine would throw. Report and stop, exactly as the retired
|
|
406
|
+
// engine returned at the offending reference node.
|
|
407
|
+
return sortDiagnostics(referenceProblems);
|
|
408
|
+
}
|
|
409
|
+
return validateInstance(schema, instance);
|
|
410
|
+
}
|
|
411
|
+
/** Validate and format in one step: the shape most callers want. */
|
|
412
|
+
export function validateToLines(schema, instance) {
|
|
413
|
+
return formatDiagnostics(validate(schema, instance));
|
|
414
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Fleet } from "./fleet.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Turn-end hook (kernel plan v1, M1-P4 step 3; R-082b, PR-209).
|
|
4
|
+
*
|
|
5
|
+
* The hook is a generated script in the TASK directory (never inside the
|
|
6
|
+
* worktree, FM-059) that the executor invokes when the payload command
|
|
7
|
+
* exits. It writes tasks/<id>/turn-end as JSON:
|
|
8
|
+
*
|
|
9
|
+
* {"endedAt": "<ISO-8601>", "exitCode": <number>}
|
|
10
|
+
*
|
|
11
|
+
* That file is the M1-P5 watcher's wake signal. It is a notification,
|
|
12
|
+
* not the task's state: meta.json status plus this exit code together
|
|
13
|
+
* are the single current-state authority (plan constraint C-1), and a
|
|
14
|
+
* MISSING turn-end file never means success (tuition T-002: an agent
|
|
15
|
+
* that dies quietly leaves no turn-end and a dirty worktree).
|
|
16
|
+
*
|
|
17
|
+
* The hook takes the payload's exit code as its single argument, so any
|
|
18
|
+
* adapter (the M1 subprocess adapter, and the window or cloud-session
|
|
19
|
+
* adapters of the M4 era) can invoke it without knowing anything else
|
|
20
|
+
* about the task. It is written with the .mjs extension so it parses as
|
|
21
|
+
* ESM regardless of the nearest package.json, since it runs inside the
|
|
22
|
+
* fleet home rather than inside this package.
|
|
23
|
+
*/
|
|
24
|
+
/** The generated hook script for a task. */
|
|
25
|
+
export declare function turnEndHookPath(fleet: Fleet, taskId: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Generate the hook script. The turn-end path is baked in as a literal,
|
|
28
|
+
* so the hook needs no fleet resolution.
|
|
29
|
+
*
|
|
30
|
+
* THE CHILD-OBSERVED POINTER RECORD (CR-B-001, the half that closes the hole
|
|
31
|
+
* rather than the half that stops mis-asserting it).
|
|
32
|
+
*
|
|
33
|
+
* `observeNames`, when given, is baked in as a literal array and the hook
|
|
34
|
+
* writes an `env` object holding what each of those names ACTUALLY IS in the
|
|
35
|
+
* environment the hook was launched with, or `null` where the name is unset.
|
|
36
|
+
* The kernel compares those against the harness-owned paths it handed over.
|
|
37
|
+
*
|
|
38
|
+
* WHY THIS IS STRONGER THAN AN ADAPTER'S REPORT AND WHY IT IS NOT PROOF.
|
|
39
|
+
* This script is written BY THE KERNEL and, for every adapter that honours
|
|
40
|
+
* the documented contract, runs in the SAME environment as the payload (the
|
|
41
|
+
* built-in adapter spreads the same `request.env` into both spawnSync calls,
|
|
42
|
+
* and M2R-004 edit 4 is the record of why a second unscrubbed launch is
|
|
43
|
+
* itself the leak). Against an adapter that does not invoke this script at
|
|
44
|
+
* all it proves nothing, and the record no longer says otherwise.
|
|
45
|
+
*
|
|
46
|
+
* THE COST SENTENCE THAT STOOD HERE IS WITHDRAWN, BECAUSE IT WAS REFUTED BY
|
|
47
|
+
* MEASUREMENT (CR-F-CRED-001, MEDIUM).
|
|
48
|
+
*
|
|
49
|
+
* It read: an adapter that quietly reverted `HOME` for the payload "has to
|
|
50
|
+
* revert it for the payload and NOT for the hook, which means launching two
|
|
51
|
+
* children with two different environments and is a substantially different
|
|
52
|
+
* act from passing a mutated copy once". Two children is one way to do it and
|
|
53
|
+
* it is not the cheap way. The cheap way is ONE child with the mutated
|
|
54
|
+
* environment plus a single `writeFileSync` of the turn-end path, which is
|
|
55
|
+
* STRICTLY LESS work than the honest path, because the honest path also
|
|
56
|
+
* spawns the hook. The turn-end path is handed to the adapter beside
|
|
57
|
+
* `hookPath`, and this generated script names it as a literal, so an adapter
|
|
58
|
+
* that never runs it can still produce a byte-identical record.
|
|
59
|
+
*
|
|
60
|
+
* WHY A NONCE DOES NOT CLOSE THIS, stated because it is the obvious repair and
|
|
61
|
+
* it was considered and refused rather than overlooked. Baking a per-task
|
|
62
|
+
* secret into this script and requiring it in the record moves the forgery
|
|
63
|
+
* from "know the path" to "read the file", and the adapter is HANDED the path
|
|
64
|
+
* of this file: it runs at the same uid, on the same filesystem, in a
|
|
65
|
+
* directory it must be able to read to invoke the hook at all. A guard whose
|
|
66
|
+
* condition the adversary can satisfy by reading one file is green and
|
|
67
|
+
* worthless, which is this repository's own recorded shape (T-008's
|
|
68
|
+
* postscript, the red-witness rule one level up). No artifact this script can
|
|
69
|
+
* write is unforgeable by a party that can read this script.
|
|
70
|
+
*
|
|
71
|
+
* So the repair is on the RECORD rather than on the check: the value is
|
|
72
|
+
* `turn-end-record`, it names the artifact the values were read from, and
|
|
73
|
+
* `CredentialHandoverRecord` (src/task.ts) says the artifact is
|
|
74
|
+
* adapter-reachable. A dishonest adapter is still not caught here, and that
|
|
75
|
+
* residue is declared at delivery/work-history/m4-p8.md item 3; what changed
|
|
76
|
+
* is that `meta.json` no longer positively asserts a child-side observation
|
|
77
|
+
* that no child made.
|
|
78
|
+
*
|
|
79
|
+
* The hook still reads no environment it was not told to read, and a hook
|
|
80
|
+
* generated with no `observeNames` behaves exactly as it did before.
|
|
81
|
+
*/
|
|
82
|
+
export declare function renderTurnEndHook(turnEndFile: string, observeNames?: readonly string[]): string;
|
|
83
|
+
/** Write the hook for a task and return its path. */
|
|
84
|
+
export declare function writeTurnEndHook(fleet: Fleet, taskId: string, observeNames?: readonly string[]): string;
|