@nac3/forge-cli 1.19.4 → 1.20.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.
Files changed (89) hide show
  1. package/dist/bin/yf.d.ts.map +1 -1
  2. package/dist/bin/yf.js +2 -1
  3. package/dist/bin/yf.js.map +1 -1
  4. package/dist/chat/instance_lock.d.ts +98 -0
  5. package/dist/chat/instance_lock.d.ts.map +1 -1
  6. package/dist/chat/instance_lock.js +135 -11
  7. package/dist/chat/instance_lock.js.map +1 -1
  8. package/dist/chat/instances.d.ts +84 -0
  9. package/dist/chat/instances.d.ts.map +1 -0
  10. package/dist/chat/instances.js +224 -0
  11. package/dist/chat/instances.js.map +1 -0
  12. package/dist/chat/panel.d.ts.map +1 -1
  13. package/dist/chat/panel.js +284 -6
  14. package/dist/chat/panel.js.map +1 -1
  15. package/dist/chat/server.d.ts.map +1 -1
  16. package/dist/chat/server.js +64 -1
  17. package/dist/chat/server.js.map +1 -1
  18. package/dist/chat/tools/evidence.d.ts.map +1 -1
  19. package/dist/chat/tools/evidence.js +7 -3
  20. package/dist/chat/tools/evidence.js.map +1 -1
  21. package/dist/chat/tools.d.ts.map +1 -1
  22. package/dist/chat/tools.js +26 -3
  23. package/dist/chat/tools.js.map +1 -1
  24. package/dist/commands/task.d.ts +13 -0
  25. package/dist/commands/task.d.ts.map +1 -1
  26. package/dist/commands/task.js +148 -3
  27. package/dist/commands/task.js.map +1 -1
  28. package/dist/commands/upgrade.d.ts +15 -0
  29. package/dist/commands/upgrade.d.ts.map +1 -1
  30. package/dist/commands/upgrade.js +63 -0
  31. package/dist/commands/upgrade.js.map +1 -1
  32. package/dist/commands/urazuke.d.ts.map +1 -1
  33. package/dist/commands/urazuke.js +68 -1
  34. package/dist/commands/urazuke.js.map +1 -1
  35. package/dist/core/forge_profile.d.ts.map +1 -1
  36. package/dist/core/forge_profile.js +41 -4
  37. package/dist/core/forge_profile.js.map +1 -1
  38. package/dist/core/whats_new.d.ts.map +1 -1
  39. package/dist/core/whats_new.js +14 -0
  40. package/dist/core/whats_new.js.map +1 -1
  41. package/dist/tasks/supervisor.d.ts +14 -0
  42. package/dist/tasks/supervisor.d.ts.map +1 -1
  43. package/dist/tasks/supervisor.js +34 -9
  44. package/dist/tasks/supervisor.js.map +1 -1
  45. package/dist/tasks/win_spawn.d.ts +60 -0
  46. package/dist/tasks/win_spawn.d.ts.map +1 -0
  47. package/dist/tasks/win_spawn.js +189 -0
  48. package/dist/tasks/win_spawn.js.map +1 -0
  49. package/dist/urazuke/current.d.ts +5 -0
  50. package/dist/urazuke/current.d.ts.map +1 -1
  51. package/dist/urazuke/current.js +14 -3
  52. package/dist/urazuke/current.js.map +1 -1
  53. package/dist/urazuke/cycle.d.ts.map +1 -1
  54. package/dist/urazuke/cycle.js +6 -2
  55. package/dist/urazuke/cycle.js.map +1 -1
  56. package/dist/urazuke/evidence.d.ts.map +1 -1
  57. package/dist/urazuke/evidence.js +9 -1
  58. package/dist/urazuke/evidence.js.map +1 -1
  59. package/dist/urazuke/freeze.d.ts +29 -0
  60. package/dist/urazuke/freeze.d.ts.map +1 -0
  61. package/dist/urazuke/freeze.js +150 -0
  62. package/dist/urazuke/freeze.js.map +1 -0
  63. package/dist/urazuke/gate.d.ts +18 -1
  64. package/dist/urazuke/gate.d.ts.map +1 -1
  65. package/dist/urazuke/gate.js +21 -0
  66. package/dist/urazuke/gate.js.map +1 -1
  67. package/dist/urazuke/gates_data.d.ts +219 -0
  68. package/dist/urazuke/gates_data.d.ts.map +1 -0
  69. package/dist/urazuke/gates_data.js +441 -0
  70. package/dist/urazuke/gates_data.js.map +1 -0
  71. package/dist/urazuke/prompts_client.d.ts +6 -0
  72. package/dist/urazuke/prompts_client.d.ts.map +1 -1
  73. package/dist/urazuke/prompts_client.js +1 -1
  74. package/dist/urazuke/prompts_client.js.map +1 -1
  75. package/dist/urazuke/receipts.d.ts +53 -1
  76. package/dist/urazuke/receipts.d.ts.map +1 -1
  77. package/dist/urazuke/receipts.js +65 -3
  78. package/dist/urazuke/receipts.js.map +1 -1
  79. package/dist/urazuke/ruleset.d.ts +56 -0
  80. package/dist/urazuke/ruleset.d.ts.map +1 -0
  81. package/dist/urazuke/ruleset.js +159 -0
  82. package/dist/urazuke/ruleset.js.map +1 -0
  83. package/dist/urazuke/start.d.ts +5 -0
  84. package/dist/urazuke/start.d.ts.map +1 -1
  85. package/dist/urazuke/start.js +39 -2
  86. package/dist/urazuke/start.js.map +1 -1
  87. package/dist/version.d.ts +1 -1
  88. package/dist/version.js +1 -1
  89. package/package.json +20 -18
@@ -0,0 +1,219 @@
1
+ import { type UrazukeArtifact, type UrazukeTramo, type UrazukeForm, type UrazukeMandatory } from './catalog.js';
2
+ import { type GatesSource } from './ruleset.js';
3
+ /** Where the method publishes it. Only `fetchPublishedGates` reads this,
4
+ * and only the explicit pull calls that. */
5
+ export declare const DEFAULT_GATES_URL = "https://yujin.app/urazuke/api/gates.json";
6
+ /** The frozen copy, inside the project. Next to the prompt overrides
7
+ * (`.forge/urazuke-prompts.json`) on purpose: the two halves of the same
8
+ * freeze belong together, and a reader looking for one finds the other.
9
+ *
10
+ * This file is meant to be COMMITTED. The prevailing convention for
11
+ * `.forge/` in a project is local state -- `workflow-config.json` is in
12
+ * .gitignore -- and the freeze has to break that convention to mean
13
+ * anything: a copy that does not travel with the repository is a freeze
14
+ * that thaws on the next clone and on every CI run, where no copy is found
15
+ * and the newest definition answers instead. A lock that only closes on one
16
+ * laptop. See docs/PROPUESTA_URAZUKE_VERSION_POR_PROYECTO_2026_10_22.md. */
17
+ export declare const GATES_FROZEN_REL: string;
18
+ export declare function frozenGatesPath(projectRoot: string): string;
19
+ /** The highest published schema this Forge knows how to read. A file that
20
+ * declares more than this is not refused -- the schema is append-only by
21
+ * the method's own rule -- but what it adds is reported as unread rather
22
+ * than silently dropped. */
23
+ export declare const MAX_SCHEMA_VERSION = 2;
24
+ /** One evaluator a step asks for. `required` decides the compatibility
25
+ * branch when this installation does not implement it. */
26
+ export interface PublishedEvaluatorRef {
27
+ name: string;
28
+ since_schema?: number;
29
+ required?: boolean;
30
+ }
31
+ /** An artifact rule as published. Same fields as the compiled
32
+ * `UrazukeArtifact` plus the schema marker the method uses to tell the
33
+ * frozen base apart from what it added afterwards. */
34
+ export interface PublishedArtifact extends UrazukeArtifact {
35
+ since_schema?: number;
36
+ anchor?: number;
37
+ }
38
+ export interface PublishedStep {
39
+ num: number;
40
+ id: string;
41
+ tramo?: string;
42
+ title: string;
43
+ summary?: string;
44
+ form?: string;
45
+ mandatory?: string;
46
+ prompt_key: string;
47
+ prompt_indexes?: number[];
48
+ inputs?: PublishedArtifact[];
49
+ artifacts?: PublishedArtifact[];
50
+ verify?: unknown[];
51
+ evaluators?: PublishedEvaluatorRef[];
52
+ }
53
+ export interface PublishedContentHash {
54
+ algo: string;
55
+ encoding: string;
56
+ canonical_form: string;
57
+ covers: string[];
58
+ excludes: string[];
59
+ value: string;
60
+ note?: string;
61
+ }
62
+ export interface PublishedGatesFile {
63
+ schema_version: number;
64
+ catalog_version: string;
65
+ content_hash: PublishedContentHash;
66
+ evaluators?: {
67
+ name: string;
68
+ since_schema?: number;
69
+ checks?: string;
70
+ }[];
71
+ compatibility?: Record<string, string>;
72
+ steps: PublishedStep[];
73
+ stations: PublishedStep[];
74
+ /** Everything else the method publishes (tramos, heuristics, adapters,
75
+ * project_config, languages, loop_entry, ...). Kept verbatim: the hash
76
+ * covers it, so dropping a key on the way in would break the seal. */
77
+ [key: string]: unknown;
78
+ }
79
+ /** The payload the declared hash covers, in the order the file declares.
80
+ * Built from `content_hash.covers` and not from a list of our own: the
81
+ * method owns which keys bear the requirement, and a consumer that keeps
82
+ * its own list stops agreeing the day the method adds one. */
83
+ export declare function coveredPayload(file: PublishedGatesFile): Record<string, unknown>;
84
+ export interface HashVerdict {
85
+ ok: boolean;
86
+ declared: string;
87
+ recomputed: string;
88
+ /** Top-level keys the file neither covers nor excludes. Not a detail: a
89
+ * key outside both lists is content nobody is protecting, and a frozen
90
+ * copy altered exactly there would still match its hash. The method
91
+ * raises its own error for this; we check it rather than trust it. */
92
+ uncovered: string[];
93
+ }
94
+ /** Recompute the content hash over the canonicalised covered payload and
95
+ * compare. The canonical form is the one the method declares
96
+ * (`json_sorted_keys_codepoint_no_whitespace_utf8`, sha256, hex lower),
97
+ * which is also the one `ruleset.ts` uses -- so one implementation serves
98
+ * both the seal and this check, and the two sides cannot drift apart. */
99
+ export declare function verifyContentHash(file: PublishedGatesFile): HashVerdict;
100
+ export interface ValidationResult {
101
+ ok: boolean;
102
+ file?: PublishedGatesFile;
103
+ problems: string[];
104
+ /** The hash verdict, when the shape was good enough to compute one. */
105
+ hash?: HashVerdict;
106
+ /** The file declares a schema this Forge does not fully know. Not a
107
+ * refusal: the schema is append-only, so an older reader stays correct
108
+ * about what it does read. It IS reported, because what it adds may
109
+ * carry requirements, and silence there is the silent damage. */
110
+ newer_schema?: number;
111
+ }
112
+ /** Validate a payload that claims to be a published gate definition.
113
+ *
114
+ * Refuses on: a broken shape, or a hash that does not recompute. The second
115
+ * one is the whole point of Pieza 2 -- a definition we cannot verify is not
116
+ * a definition we may enforce, because the only thing that makes a frozen
117
+ * copy trustworthy offline is that its content reproduces its own seal. */
118
+ export declare function validatePublishedGates(raw: unknown): ValidationResult;
119
+ export interface ResolvedEvaluator {
120
+ name: string;
121
+ required: boolean;
122
+ since_schema: number;
123
+ }
124
+ /** One step or station, in the one shape `gate.ts` evaluates -- whether it
125
+ * came from the compiled catalogue or from a published file. */
126
+ export interface ResolvedGateStep {
127
+ num: number;
128
+ id: string;
129
+ title: string;
130
+ tramo?: UrazukeTramo | string;
131
+ summary?: string;
132
+ form?: UrazukeForm | string;
133
+ mandatory?: UrazukeMandatory | string;
134
+ prompt_key: string;
135
+ inputs: UrazukeArtifact[];
136
+ artifacts: UrazukeArtifact[];
137
+ verify: string[];
138
+ evaluators: ResolvedEvaluator[];
139
+ }
140
+ export interface ResolvedGates {
141
+ source: GatesSource;
142
+ version: string;
143
+ /** Recomputed here. For `compiled` it is the requirement hash of
144
+ * catalog.ts; for a file, its verified content hash. The two are
145
+ * different domains and must not be compared across sources -- the
146
+ * source is part of the identity, which is why the stamp carries both. */
147
+ hash: string;
148
+ schema_version: number;
149
+ steps: ResolvedGateStep[];
150
+ stations: ResolvedGateStep[];
151
+ /** The evaluator vocabulary the definition declares. Informative: what
152
+ * decides is whether THIS Forge implements each name. */
153
+ declared_evaluators: string[];
154
+ /** Things the reader wants said out loud: a refused frozen copy, a schema
155
+ * newer than this reader, a definition with no artifacts. Never swallowed
156
+ * -- a resolution that quietly fell back is the bug this module exists
157
+ * to prevent. */
158
+ notices: string[];
159
+ }
160
+ /** The published definition, in the resolved shape. */
161
+ export declare function publishedToResolved(file: PublishedGatesFile, source: GatesSource, hash: string): ResolvedGates;
162
+ /** Forge's own compiled catalogue, in the same resolved shape.
163
+ *
164
+ * This is the BASE: what Forge enforced before the method took ownership,
165
+ * and what a project freezes when it is started by a Forge that has not
166
+ * pulled a published definition. Every step declares exactly one evaluator
167
+ * and every one of them is required -- the compiled catalogue has no notion
168
+ * of an optional evaluator, which is correct, because it also has no
169
+ * evaluator it cannot measure. */
170
+ export declare function compiledGatesResolved(): ResolvedGates;
171
+ export interface FrozenReadResult {
172
+ /** The definition, when the file was there AND verified. */
173
+ gates?: ResolvedGates;
174
+ /** No file: the normal case for a project that has not been frozen. */
175
+ absent: boolean;
176
+ /** The file was there and was REFUSED. The reason is for the owner, not
177
+ * for a log nobody reads: a refused freeze means the project is being
178
+ * judged by the compiled rule and not by the one it thought it froze. */
179
+ refused?: string[];
180
+ }
181
+ export declare function readFrozenGates(projectRoot: string): Promise<FrozenReadResult>;
182
+ /** Write the frozen copy. Atomic (tmp + rename) for the same reason the
183
+ * prompt overrides are: a half-written definition read by the next turn is
184
+ * a project with no rule at all. */
185
+ export declare function writeFrozenGates(projectRoot: string, file: PublishedGatesFile): Promise<void>;
186
+ /** Serialise the compiled catalogue INTO the published shape, sealed with a
187
+ * hash computed the published way, so a frozen copy of the base is read
188
+ * back by exactly the same path as a frozen copy of the portal's.
189
+ *
190
+ * This is what "freeze every existing project with what it reads today"
191
+ * writes: the base, unchanged, with nothing added. The first day the
192
+ * difference is exactly zero and it can be demonstrated -- that is the
193
+ * criterion the whole proposal rests on. */
194
+ export declare function compiledAsPublishedFile(): PublishedGatesFile;
195
+ /** WHICH gate definition is in force for this project.
196
+ *
197
+ * Precedence: the project's frozen copy, then the compiled catalogue. The
198
+ * portal is deliberately absent -- see rule 1 at the top of this file. A
199
+ * refused frozen copy does NOT promote the portal and does not fail the
200
+ * read: it falls back to the compiled catalogue, which is the safe state
201
+ * (nothing can tighten without an install), and it says so. */
202
+ export declare function resolveGates(projectRoot?: string): Promise<ResolvedGates>;
203
+ export interface FetchGatesOptions {
204
+ fetchImpl?: typeof fetch;
205
+ url?: string;
206
+ timeout_ms?: number;
207
+ }
208
+ /** Bring the published definition over the network.
209
+ *
210
+ * NOT part of `resolveGates`, on purpose and by decision: the owner asked
211
+ * that a project start and finish on the same version of the method, so
212
+ * the only legitimate caller is the explicit double lock (Pieza 4), which
213
+ * has to show how many green steps would turn red BEFORE it writes
214
+ * anything. A validated file or nothing -- an unverifiable definition is
215
+ * never handed back. */
216
+ export declare function fetchPublishedGates(opts?: FetchGatesOptions): Promise<ValidationResult>;
217
+ /** One line for a report or the CLI. */
218
+ export declare function renderGatesSource(g: ResolvedGates): string;
219
+ //# sourceMappingURL=gates_data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gates_data.d.ts","sourceRoot":"","sources":["../../src/urazuke/gates_data.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,441 @@
1
+ /**
2
+ * THE GATE DEFINITION AS DATA -- reading the gates the method publishes
3
+ * (PND-165, Pieza 2).
4
+ *
5
+ * Until 2026-10-23 the mechanical definition of the gates lived compiled
6
+ * inside Forge (`catalog.ts`): 24 steps, 8 stations, one evaluator each. On
7
+ * that day the method took ownership of it and started publishing it as
8
+ * data (D-050), the same way it already published the prompts. So a
9
+ * requirement of the method no longer waits for a Forge release -- which is
10
+ * the point -- and a project can now be re-read under a rule that did not
11
+ * exist when its evidence was produced, which is the danger. The owner's
12
+ * decision bounds that: a project starts and finishes on the same version of
13
+ * the method unless somebody presses the double lock.
14
+ *
15
+ * This module is the READ side of that decision. Three rules it enforces,
16
+ * each one because its absence turns the freeze into a label:
17
+ *
18
+ * 1. PRECEDENCE: the copy frozen in the project wins over everything.
19
+ * Second, and only second, Forge's own compiled catalogue. The portal
20
+ * is NOT on this path -- `fetchPublishedGates` exists for the explicit
21
+ * pull (Pieza 4) and nothing here calls it. A read that can reach the
22
+ * network is a read that can change a project's rule between two
23
+ * turns, and no decision asked for that.
24
+ *
25
+ * 2. THE HASH IS RECOMPUTED, NEVER READ. A frozen copy edited by hand or
26
+ * truncated by a merge has to be detectable without the network. A
27
+ * stamp that only repeats what the file claims about itself is a
28
+ * rotulo, not a seal (adenda 2). A frozen copy whose recomputed hash
29
+ * disagrees with its own declared value is REFUSED, loudly, and the
30
+ * compiled catalogue answers instead -- a tampered rule must never be
31
+ * the rule in force.
32
+ *
33
+ * 3. AN UNKNOWN EVALUATOR IS NOT A GREEN AND NOT AN APPROXIMATION. The
34
+ * published catalogue already declares twelve evaluators and this
35
+ * Forge implements eight. The method's own compatibility clause says
36
+ * what to do: a REQUIRED evaluator this installation cannot measure is
37
+ * reported as "declared, not measured by this installation", naming it,
38
+ * and the step does not advance. Approximating with the closest
39
+ * evaluator, or ignoring the block, is forbidden. Zero silent damage.
40
+ *
41
+ * What this module does NOT do: evaluate. It resolves WHICH definition is in
42
+ * force and hands it over in one shape, whatever its source. `gate.ts`
43
+ * measures.
44
+ *
45
+ * ASCII-only.
46
+ */
47
+ import fs from 'node:fs/promises';
48
+ import path from 'node:path';
49
+ import { URAZUKE_STEPS, URAZUKE_LEGACY_STATIONS, } from './catalog.js';
50
+ import { canonicalJson, sha256Hex, compiledGatesHash, FORGE_COMPILED_GATES_VERSION, } from './ruleset.js';
51
+ /** Where the method publishes it. Only `fetchPublishedGates` reads this,
52
+ * and only the explicit pull calls that. */
53
+ export const DEFAULT_GATES_URL = 'https://yujin.app/urazuke/api/gates.json';
54
+ /** The frozen copy, inside the project. Next to the prompt overrides
55
+ * (`.forge/urazuke-prompts.json`) on purpose: the two halves of the same
56
+ * freeze belong together, and a reader looking for one finds the other.
57
+ *
58
+ * This file is meant to be COMMITTED. The prevailing convention for
59
+ * `.forge/` in a project is local state -- `workflow-config.json` is in
60
+ * .gitignore -- and the freeze has to break that convention to mean
61
+ * anything: a copy that does not travel with the repository is a freeze
62
+ * that thaws on the next clone and on every CI run, where no copy is found
63
+ * and the newest definition answers instead. A lock that only closes on one
64
+ * laptop. See docs/PROPUESTA_URAZUKE_VERSION_POR_PROYECTO_2026_10_22.md. */
65
+ export const GATES_FROZEN_REL = path.join('.forge', 'urazuke-gates.json');
66
+ export function frozenGatesPath(projectRoot) {
67
+ return path.join(projectRoot, GATES_FROZEN_REL);
68
+ }
69
+ /** The highest published schema this Forge knows how to read. A file that
70
+ * declares more than this is not refused -- the schema is append-only by
71
+ * the method's own rule -- but what it adds is reported as unread rather
72
+ * than silently dropped. */
73
+ export const MAX_SCHEMA_VERSION = 2;
74
+ /* ---------------- the hash ---------------- */
75
+ /** The payload the declared hash covers, in the order the file declares.
76
+ * Built from `content_hash.covers` and not from a list of our own: the
77
+ * method owns which keys bear the requirement, and a consumer that keeps
78
+ * its own list stops agreeing the day the method adds one. */
79
+ export function coveredPayload(file) {
80
+ const out = {};
81
+ for (const k of file.content_hash.covers)
82
+ out[k] = file[k];
83
+ return out;
84
+ }
85
+ /** Recompute the content hash over the canonicalised covered payload and
86
+ * compare. The canonical form is the one the method declares
87
+ * (`json_sorted_keys_codepoint_no_whitespace_utf8`, sha256, hex lower),
88
+ * which is also the one `ruleset.ts` uses -- so one implementation serves
89
+ * both the seal and this check, and the two sides cannot drift apart. */
90
+ export function verifyContentHash(file) {
91
+ const recomputed = sha256Hex(canonicalJson(coveredPayload(file)));
92
+ const accounted = new Set([...file.content_hash.covers, ...file.content_hash.excludes]);
93
+ const uncovered = Object.keys(file).filter((k) => !accounted.has(k)).sort();
94
+ return {
95
+ ok: recomputed === file.content_hash.value && uncovered.length === 0,
96
+ declared: file.content_hash.value,
97
+ recomputed,
98
+ uncovered,
99
+ };
100
+ }
101
+ /* ---------------- validation ---------------- */
102
+ function isArtifactArray(x) {
103
+ return Array.isArray(x) && x.every((a) => a && typeof a === 'object' && typeof a.path === 'string');
104
+ }
105
+ function validStepArray(x, label, problems) {
106
+ if (!Array.isArray(x) || !x.length) {
107
+ problems.push(label + ': missing or empty');
108
+ return false;
109
+ }
110
+ let ok = true;
111
+ for (const s of x) {
112
+ if (!s || typeof s !== 'object') {
113
+ problems.push(label + ': a non-object entry');
114
+ ok = false;
115
+ continue;
116
+ }
117
+ if (typeof s.id !== 'string' || !s.id) {
118
+ problems.push(label + ': an entry with no id');
119
+ ok = false;
120
+ }
121
+ if (typeof s.num !== 'number') {
122
+ problems.push(label + ' ' + s.id + ': num is not a number');
123
+ ok = false;
124
+ }
125
+ if (typeof s.title !== 'string') {
126
+ problems.push(label + ' ' + s.id + ': title is not a string');
127
+ ok = false;
128
+ }
129
+ if (s.inputs !== undefined && !isArtifactArray(s.inputs)) {
130
+ problems.push(label + ' ' + s.id + ': inputs is not a list of artifacts');
131
+ ok = false;
132
+ }
133
+ if (s.artifacts !== undefined && !isArtifactArray(s.artifacts)) {
134
+ problems.push(label + ' ' + s.id + ': artifacts is not a list of artifacts');
135
+ ok = false;
136
+ }
137
+ if (s.evaluators !== undefined) {
138
+ if (!Array.isArray(s.evaluators) || !s.evaluators.every((e) => e && typeof e.name === 'string')) {
139
+ problems.push(label + ' ' + s.id + ': evaluators is not a list of {name}');
140
+ ok = false;
141
+ }
142
+ }
143
+ }
144
+ return ok;
145
+ }
146
+ /** Validate a payload that claims to be a published gate definition.
147
+ *
148
+ * Refuses on: a broken shape, or a hash that does not recompute. The second
149
+ * one is the whole point of Pieza 2 -- a definition we cannot verify is not
150
+ * a definition we may enforce, because the only thing that makes a frozen
151
+ * copy trustworthy offline is that its content reproduces its own seal. */
152
+ export function validatePublishedGates(raw) {
153
+ const problems = [];
154
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
155
+ return { ok: false, problems: ['not a JSON object'] };
156
+ }
157
+ const f = raw;
158
+ if (typeof f.schema_version !== 'number')
159
+ problems.push('schema_version missing or not a number');
160
+ if (typeof f.catalog_version !== 'string' || !f.catalog_version)
161
+ problems.push('catalog_version missing');
162
+ const ch = f.content_hash;
163
+ if (!ch || typeof ch !== 'object' || typeof ch.value !== 'string'
164
+ || !Array.isArray(ch.covers) || !Array.isArray(ch.excludes)) {
165
+ problems.push('content_hash missing or incomplete (needs value + covers + excludes)');
166
+ }
167
+ else {
168
+ if (ch.algo && ch.algo !== 'sha256')
169
+ problems.push('content_hash.algo is ' + ch.algo + ', and this reader only does sha256');
170
+ if (ch.canonical_form && ch.canonical_form !== 'json_sorted_keys_codepoint_no_whitespace_utf8') {
171
+ problems.push('content_hash.canonical_form is ' + ch.canonical_form + ', not the form this reader canonicalises to');
172
+ }
173
+ }
174
+ validStepArray(f.steps, 'steps', problems);
175
+ validStepArray(f.stations, 'stations', problems);
176
+ if (problems.length)
177
+ return { ok: false, problems };
178
+ const hash = verifyContentHash(f);
179
+ if (!hash.ok) {
180
+ if (hash.recomputed !== hash.declared) {
181
+ problems.push('content hash does not recompute: declared ' + hash.declared.slice(0, 16)
182
+ + ', recomputed ' + hash.recomputed.slice(0, 16)
183
+ + ' -- the content and its seal disagree, so this definition cannot be trusted');
184
+ }
185
+ if (hash.uncovered.length) {
186
+ problems.push('top-level key(s) neither covered nor excluded by content_hash: ' + hash.uncovered.join(', ')
187
+ + ' -- content nobody protects, so an edit there would still match the seal');
188
+ }
189
+ return { ok: false, problems, hash };
190
+ }
191
+ const res = { ok: true, file: f, problems, hash };
192
+ if (f.schema_version > MAX_SCHEMA_VERSION)
193
+ res.newer_schema = f.schema_version;
194
+ return res;
195
+ }
196
+ function artifactsOf(xs) {
197
+ return (xs ?? []).map((a) => ({
198
+ path: a.path,
199
+ ...(a.must_contain ? { must_contain: [...a.must_contain] } : {}),
200
+ ...(a.should_contain ? { should_contain: [...a.should_contain] } : {}),
201
+ ...(a.min_bytes != null ? { min_bytes: a.min_bytes } : {}),
202
+ ...(a.fresh_days != null ? { fresh_days: a.fresh_days } : {}),
203
+ ...(a.label ? { label: a.label } : {}),
204
+ }));
205
+ }
206
+ /** `verify` entries are strings in the base and objects with `{text,
207
+ * since_schema, anchor}` in what the method added. Both are owner prose;
208
+ * flatten to text so a consumer does not have to know the difference. */
209
+ function verifyOf(xs) {
210
+ const out = [];
211
+ for (const v of xs ?? []) {
212
+ if (typeof v === 'string')
213
+ out.push(v);
214
+ else if (v && typeof v === 'object' && typeof v.text === 'string') {
215
+ out.push(v.text);
216
+ }
217
+ }
218
+ return out;
219
+ }
220
+ function evaluatorsOf(xs) {
221
+ return (xs ?? []).map((e) => ({
222
+ name: e.name,
223
+ required: e.required !== false,
224
+ since_schema: e.since_schema ?? 1,
225
+ }));
226
+ }
227
+ function publishedStepToResolved(s) {
228
+ return {
229
+ num: s.num,
230
+ id: s.id,
231
+ title: s.title,
232
+ ...(s.tramo ? { tramo: s.tramo } : {}),
233
+ ...(s.summary ? { summary: s.summary } : {}),
234
+ ...(s.form ? { form: s.form } : {}),
235
+ ...(s.mandatory ? { mandatory: s.mandatory } : {}),
236
+ prompt_key: s.prompt_key,
237
+ inputs: artifactsOf(s.inputs),
238
+ artifacts: artifactsOf(s.artifacts),
239
+ verify: verifyOf(s.verify),
240
+ evaluators: evaluatorsOf(s.evaluators),
241
+ };
242
+ }
243
+ /** The published definition, in the resolved shape. */
244
+ export function publishedToResolved(file, source, hash) {
245
+ const notices = [];
246
+ if (file.schema_version > MAX_SCHEMA_VERSION) {
247
+ notices.push('the definition declares schema ' + file.schema_version + ' and this Forge reads up to '
248
+ + MAX_SCHEMA_VERSION + ': what schema ' + file.schema_version + ' adds is not read here. '
249
+ + 'Nothing is approximated -- an evaluator this installation does not implement is reported as declared, not measured.');
250
+ }
251
+ const declared = (file.evaluators ?? []).map((e) => e.name).filter((n) => typeof n === 'string').sort();
252
+ return {
253
+ source,
254
+ version: file.catalog_version,
255
+ hash,
256
+ schema_version: file.schema_version,
257
+ steps: file.steps.map(publishedStepToResolved),
258
+ stations: (file.stations ?? []).map(publishedStepToResolved),
259
+ declared_evaluators: declared,
260
+ notices,
261
+ };
262
+ }
263
+ /** Forge's own compiled catalogue, in the same resolved shape.
264
+ *
265
+ * This is the BASE: what Forge enforced before the method took ownership,
266
+ * and what a project freezes when it is started by a Forge that has not
267
+ * pulled a published definition. Every step declares exactly one evaluator
268
+ * and every one of them is required -- the compiled catalogue has no notion
269
+ * of an optional evaluator, which is correct, because it also has no
270
+ * evaluator it cannot measure. */
271
+ export function compiledGatesResolved() {
272
+ const one = (name) => [{ name, required: true, since_schema: 1 }];
273
+ return {
274
+ source: 'compiled',
275
+ version: FORGE_COMPILED_GATES_VERSION,
276
+ hash: compiledGatesHash(),
277
+ schema_version: 1,
278
+ steps: URAZUKE_STEPS.map((s) => ({
279
+ num: s.num, id: s.id, title: s.title, tramo: s.tramo, summary: s.summary,
280
+ form: s.form, mandatory: s.mandatory, prompt_key: s.prompt_key,
281
+ inputs: s.inputs.map((a) => ({ ...a })),
282
+ artifacts: s.artifacts.map((a) => ({ ...a })),
283
+ verify: [...s.verify],
284
+ evaluators: one(s.evaluator),
285
+ })),
286
+ stations: URAZUKE_LEGACY_STATIONS.map((s) => ({
287
+ num: s.num, id: s.id, title: s.title, summary: s.summary, prompt_key: s.prompt_key,
288
+ inputs: [],
289
+ artifacts: s.artifacts.map((a) => ({ ...a })),
290
+ verify: [...s.verify],
291
+ evaluators: one(s.evaluator),
292
+ })),
293
+ declared_evaluators: [...new Set([
294
+ ...URAZUKE_STEPS.map((s) => s.evaluator),
295
+ ...URAZUKE_LEGACY_STATIONS.map((s) => s.evaluator),
296
+ ])].sort(),
297
+ notices: [],
298
+ };
299
+ }
300
+ export async function readFrozenGates(projectRoot) {
301
+ let text;
302
+ try {
303
+ text = await fs.readFile(frozenGatesPath(projectRoot), 'utf8');
304
+ }
305
+ catch {
306
+ return { absent: true };
307
+ }
308
+ let raw;
309
+ try {
310
+ raw = JSON.parse(text);
311
+ }
312
+ catch (e) {
313
+ return { absent: false, refused: ['the frozen copy is not valid JSON: ' + e.message] };
314
+ }
315
+ const v = validatePublishedGates(raw);
316
+ if (!v.ok || !v.file || !v.hash)
317
+ return { absent: false, refused: v.problems };
318
+ return { absent: false, gates: publishedToResolved(v.file, 'project', v.hash.recomputed) };
319
+ }
320
+ /** Write the frozen copy. Atomic (tmp + rename) for the same reason the
321
+ * prompt overrides are: a half-written definition read by the next turn is
322
+ * a project with no rule at all. */
323
+ export async function writeFrozenGates(projectRoot, file) {
324
+ const f = frozenGatesPath(projectRoot);
325
+ await fs.mkdir(path.dirname(f), { recursive: true });
326
+ const tmp = f + '.tmp';
327
+ await fs.writeFile(tmp, JSON.stringify(file, null, 2) + '\n', 'utf8');
328
+ await fs.rename(tmp, f);
329
+ }
330
+ /** Serialise the compiled catalogue INTO the published shape, sealed with a
331
+ * hash computed the published way, so a frozen copy of the base is read
332
+ * back by exactly the same path as a frozen copy of the portal's.
333
+ *
334
+ * This is what "freeze every existing project with what it reads today"
335
+ * writes: the base, unchanged, with nothing added. The first day the
336
+ * difference is exactly zero and it can be demonstrated -- that is the
337
+ * criterion the whole proposal rests on. */
338
+ export function compiledAsPublishedFile() {
339
+ const r = compiledGatesResolved();
340
+ const body = {
341
+ schema_version: 1,
342
+ catalog_version: FORGE_COMPILED_GATES_VERSION,
343
+ evaluators: r.declared_evaluators.map((name) => ({ name, since_schema: 1 })),
344
+ steps: r.steps.map((s) => ({
345
+ num: s.num, id: s.id, tramo: s.tramo, title: s.title, summary: s.summary,
346
+ form: s.form, mandatory: s.mandatory, prompt_key: s.prompt_key,
347
+ inputs: s.inputs, artifacts: s.artifacts, verify: s.verify,
348
+ evaluators: s.evaluators.map((e) => ({ name: e.name, since_schema: e.since_schema, required: e.required })),
349
+ })),
350
+ stations: r.stations.map((s) => ({
351
+ num: s.num, id: s.id, title: s.title, summary: s.summary, prompt_key: s.prompt_key,
352
+ artifacts: s.artifacts, verify: s.verify,
353
+ evaluators: s.evaluators.map((e) => ({ name: e.name, since_schema: e.since_schema, required: e.required })),
354
+ })),
355
+ };
356
+ const covers = ['schema_version', 'catalog_version', 'evaluators', 'steps', 'stations'];
357
+ const excludes = ['content_hash', 'what', 'frozen_at', 'frozen_from'];
358
+ const payload = {};
359
+ for (const k of covers)
360
+ payload[k] = body[k];
361
+ const file = {
362
+ what: 'The gate definition frozen for this project (PND-165). Forge reads THIS'
363
+ + ' before its own compiled catalogue and before the portal. It is meant to be'
364
+ + ' committed: a copy that does not travel with the repository thaws on the next'
365
+ + ' clone. Replaced only by the explicit double lock, never on its own.',
366
+ ...body,
367
+ content_hash: {
368
+ algo: 'sha256',
369
+ encoding: 'hex_lower',
370
+ canonical_form: 'json_sorted_keys_codepoint_no_whitespace_utf8',
371
+ covers,
372
+ excludes,
373
+ value: sha256Hex(canonicalJson(payload)),
374
+ note: 'Recomputed on every read, never trusted as read: that is what makes a'
375
+ + ' hand-edited or merge-truncated copy detectable without the network.',
376
+ },
377
+ };
378
+ return file;
379
+ }
380
+ /* ---------------- resolution ---------------- */
381
+ /** WHICH gate definition is in force for this project.
382
+ *
383
+ * Precedence: the project's frozen copy, then the compiled catalogue. The
384
+ * portal is deliberately absent -- see rule 1 at the top of this file. A
385
+ * refused frozen copy does NOT promote the portal and does not fail the
386
+ * read: it falls back to the compiled catalogue, which is the safe state
387
+ * (nothing can tighten without an install), and it says so. */
388
+ export async function resolveGates(projectRoot) {
389
+ if (projectRoot) {
390
+ const frozen = await readFrozenGates(projectRoot);
391
+ if (frozen.gates)
392
+ return frozen.gates;
393
+ if (frozen.refused?.length) {
394
+ const base = compiledGatesResolved();
395
+ return {
396
+ ...base,
397
+ notices: [
398
+ 'the frozen gate definition of this project was REFUSED and the compiled catalogue is'
399
+ + ' judging instead: ' + frozen.refused.join('; ')
400
+ + '. Until it is restored, this project is not being measured by the version it froze.',
401
+ ...base.notices,
402
+ ],
403
+ };
404
+ }
405
+ }
406
+ return compiledGatesResolved();
407
+ }
408
+ /** Bring the published definition over the network.
409
+ *
410
+ * NOT part of `resolveGates`, on purpose and by decision: the owner asked
411
+ * that a project start and finish on the same version of the method, so
412
+ * the only legitimate caller is the explicit double lock (Pieza 4), which
413
+ * has to show how many green steps would turn red BEFORE it writes
414
+ * anything. A validated file or nothing -- an unverifiable definition is
415
+ * never handed back. */
416
+ export async function fetchPublishedGates(opts = {}) {
417
+ const url = opts.url ?? process.env['YF_URAZUKE_GATES_URL'] ?? DEFAULT_GATES_URL;
418
+ const impl = opts.fetchImpl ?? fetch;
419
+ const ctrl = new AbortController();
420
+ const timer = setTimeout(() => ctrl.abort(), opts.timeout_ms ?? 10000);
421
+ try {
422
+ const r = await impl(url, { signal: ctrl.signal });
423
+ if (!r.ok)
424
+ return { ok: false, problems: ['GET ' + url + ' answered ' + r.status] };
425
+ return validatePublishedGates(await r.json());
426
+ }
427
+ catch (e) {
428
+ return { ok: false, problems: ['GET ' + url + ' failed: ' + e.message] };
429
+ }
430
+ finally {
431
+ clearTimeout(timer);
432
+ }
433
+ }
434
+ /* ---------------- what this installation can measure ---------------- */
435
+ /** One line for a report or the CLI. */
436
+ export function renderGatesSource(g) {
437
+ return g.version + ' [' + g.source + '] schema ' + g.schema_version
438
+ + ' hash ' + g.hash.slice(0, 12)
439
+ + ' -- ' + g.steps.length + ' steps, ' + g.stations.length + ' stations';
440
+ }
441
+ //# sourceMappingURL=gates_data.js.map