arkgate 4.8.24 → 4.8.25
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 +85 -0
- package/README.md +2 -2
- package/bin/ark-check-runtime.mjs +17 -14
- package/bin/ark-layer-match.mjs +115 -13
- package/bin/ark-mcp-runtime.mjs +36 -31
- package/bin/ark-shared.mjs +4 -23
- package/bin/lib/adoption-stance.mjs +2 -2
- package/bin/lib/adr-path.mjs +3 -3
- package/bin/lib/agent-gates.mjs +0 -61
- package/bin/lib/agent-homes.mjs +12 -21
- package/bin/lib/agent-projection-command.mjs +4 -4
- package/bin/lib/ambient-state.mjs +9 -30
- package/bin/lib/analysis-engine.mjs +14 -14
- package/bin/lib/architecture-scan.mjs +11 -9
- package/bin/lib/ark-order-doctor.mjs +3 -28
- package/bin/lib/ark-order-facts.mjs +6 -99
- package/bin/lib/ark-order-types.mjs +1 -2
- package/bin/lib/ark-run-doctor.mjs +3 -28
- package/bin/lib/ark-run-facts.mjs +30 -40
- package/bin/lib/ark-run-sensors.mjs +1 -1
- package/bin/lib/arkrule-file-hints.mjs +2 -47
- package/bin/lib/arkrules-contract.mjs +124 -133
- package/bin/lib/arkrules-sensors.mjs +7 -5
- package/bin/lib/check-args.mjs +63 -1
- package/bin/lib/ci-and-commands.mjs +8 -72
- package/bin/lib/ci-merge-boundary.mjs +1 -1
- package/bin/lib/class-source-scan.mjs +5 -5
- package/bin/lib/clone-detection.mjs +474 -0
- package/bin/lib/codex-home.mjs +1 -1
- package/bin/lib/config-contract-slices.mjs +44 -6
- package/bin/lib/config-contract.mjs +40 -117
- package/bin/lib/config-extras.mjs +16 -22
- package/bin/lib/contract-smells.mjs +33 -15
- package/bin/lib/core-ratchet.mjs +0 -2
- package/bin/lib/dashboard-cli.mjs +2 -2
- package/bin/lib/deep-module-coach.mjs +2 -4
- package/bin/lib/deploy-path.mjs +13 -38
- package/bin/lib/design-smells.mjs +2 -2
- package/bin/lib/diagnostic-catalog.mjs +8 -1
- package/bin/lib/doctor-advisories.mjs +55 -9
- package/bin/lib/doctor-green-cite.mjs +1 -1
- package/bin/lib/doctor-human.mjs +1 -1
- package/bin/lib/doctor-next-actions.mjs +2 -2
- package/bin/lib/doctor-plan.mjs +4 -6
- package/bin/lib/duplication-io.mjs +572 -0
- package/bin/lib/effective-contract-load.mjs +181 -44
- package/bin/lib/entry-points-io.mjs +424 -0
- package/bin/lib/extra-merge-teeth.mjs +36 -0
- package/bin/lib/field-install.mjs +38 -14
- package/bin/lib/first-run-help.mjs +7 -3
- package/bin/lib/flat-parent-importers.mjs +5 -3
- package/bin/lib/gate-files.mjs +30 -89
- package/bin/lib/github-enforcement.mjs +165 -28
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/hook-templates.mjs +16 -16
- package/bin/lib/html-report-advisories.mjs +4 -0
- package/bin/lib/html-report-depth.mjs +2 -2
- package/bin/lib/html-report.mjs +2 -2
- package/bin/lib/import-graph-projection.mjs +112 -0
- package/bin/lib/improvement-compass-doctor.mjs +1 -24
- package/bin/lib/invariant-coverage-io.mjs +4 -3
- package/bin/lib/invariant-coverage.mjs +26 -0
- package/bin/lib/invariant-probe-cli.mjs +347 -0
- package/bin/lib/invariant-probe-io.mjs +165 -0
- package/bin/lib/invariant-probe-runner.mjs +268 -0
- package/bin/lib/invariant-probe-sites.mjs +174 -0
- package/bin/lib/invariant-probe-workspace.mjs +183 -0
- package/bin/lib/invariant-probe.mjs +461 -0
- package/bin/lib/invariant-tests-path.mjs +16 -22
- package/bin/lib/literal-path-drift.mjs +4 -2
- package/bin/lib/managed-upgrade.mjs +2 -8
- package/bin/lib/mcp-adoption.mjs +4 -5
- package/bin/lib/mcp-hook-payload.mjs +4 -4
- package/bin/lib/orphan-modules-io.mjs +458 -0
- package/bin/lib/orphan-modules.mjs +251 -0
- package/bin/lib/outside-importers.mjs +197 -0
- package/bin/lib/package-manager.mjs +3 -3
- package/bin/lib/parse-health.mjs +1 -1
- package/bin/lib/persistence-write-hint.mjs +33 -8
- package/bin/lib/pin-dependent-gates.mjs +2 -2
- package/bin/lib/policy-delta-io.mjs +55 -8
- package/bin/lib/post-green-path.mjs +1 -1
- package/bin/lib/prepare-change.mjs +3 -9
- package/bin/lib/prepare-write.mjs +1 -2
- package/bin/lib/presets.mjs +56 -68
- package/bin/lib/product-copy.mjs +1 -6
- package/bin/lib/project-root.mjs +31 -62
- package/bin/lib/resolved-candidate-facts.mjs +90 -62
- package/bin/lib/rules-under-contract.mjs +72 -8
- package/bin/lib/scan-files.mjs +4 -4
- package/bin/lib/schema-validation.mjs +130 -0
- package/bin/lib/sensor-promote-cli.mjs +2 -11
- package/bin/lib/sensor-promote-io.mjs +33 -22
- package/bin/lib/sensor-promotion.mjs +1 -1
- package/bin/lib/skill-catalog-apply.mjs +1 -1
- package/bin/lib/skill-install.mjs +42 -38
- package/bin/lib/snippet-analysis.mjs +1 -1
- package/bin/lib/start-install-recovery.mjs +1 -1
- package/bin/lib/start-preview.mjs +1 -1
- package/bin/lib/states-transitions-presence.mjs +3 -3
- package/bin/lib/status-command.mjs +2 -18
- package/bin/lib/status-manifest.mjs +18 -26
- package/bin/lib/status-transition-catalog.mjs +0 -6
- package/bin/lib/suggestions.mjs +3 -3
- package/bin/lib/team-parliament-io.mjs +3 -17
- package/bin/lib/team-parliament.mjs +15 -9
- package/bin/lib/upgrade-behavior-changes.mjs +2 -2
- package/bin/lib/upgrade-command.mjs +2 -24
- package/bin/lib/upgrade-outcome.mjs +1 -1
- package/bin/lib/upstream-report.mjs +21 -10
- package/bin/lib/violations.mjs +10 -9
- package/bin/lib/weakest-link.mjs +13 -0
- package/bin/lib/write-path-capabilities.mjs +2 -2
- package/dist/chunk-AHACEKSV.js +16 -0
- package/dist/chunk-UN5EH4QV.cjs +16 -0
- package/dist/{configTypes-BJbaeoj4.d.cts → configTypes-GPvvdleM.d.cts} +12 -1
- package/dist/{configTypes-BJbaeoj4.d.ts → configTypes-GPvvdleM.d.ts} +12 -1
- package/dist/eslint/index.cjs +11 -11
- package/dist/eslint/index.d.cts +10 -1
- package/dist/eslint/index.d.ts +10 -1
- package/dist/eslint/index.js +11 -11
- package/dist/index.cjs +34 -34
- package/dist/index.d.cts +587 -515
- package/dist/index.d.ts +587 -515
- package/dist/index.js +34 -34
- package/dist/nestjs/index.cjs +1 -1
- package/dist/nestjs/index.d.cts +1 -1
- package/dist/nestjs/index.d.ts +1 -1
- package/dist/nestjs/index.js +1 -1
- package/dist/order/index.cjs +1 -1
- package/dist/order/index.js +1 -1
- package/dist/runtime/index.cjs +12 -12
- package/dist/runtime/index.d.cts +174 -136
- package/dist/runtime/index.d.ts +174 -136
- package/dist/runtime/index.js +12 -12
- package/dist/{types-DRinG-vT.d.cts → types-CnauMQQL.d.cts} +33 -25
- package/dist/{types-DRinG-vT.d.ts → types-CnauMQQL.d.ts} +33 -25
- package/docs/README.md +2 -1
- package/docs/agent-guide.md +134 -2
- package/docs/ai-gates.md +15 -1
- package/docs/configuration.md +40 -11
- package/docs/diagnostics.md +118 -2
- package/docs/package-surface.md +4 -1
- package/docs/product-voice.md +7 -1
- package/package.json +11 -2
- package/schemas/ark.config.schema.json +31 -3
- package/schemas/ark.invariant-probe.schema.json +368 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +1 -0
- package/templates/agent-skills/ark-contract/SKILL.md +17 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
- package/templates/agent-skills/ark-explore/SKILL.md +7 -0
- package/templates/agent-skills/ark-fix/SKILL.md +20 -0
- package/templates/agent-skills/ark-place/SKILL.md +8 -0
- package/templates/skills/ark-adopt.md +1 -0
- package/templates/skills/ark-contract.md +17 -0
- package/templates/skills/ark-coverage.md +2 -0
- package/templates/skills/ark-explore.md +7 -0
- package/templates/skills/ark-fix.md +20 -0
- package/templates/skills/ark-place.md +8 -0
- package/bin/lib/ark-order-error.mjs +0 -18
- package/bin/lib/ark-order-invariants.mjs +0 -388
- package/bin/lib/stable-hash.mjs +0 -90
- package/dist/chunk-MBXCFLO4.js +0 -16
- package/dist/chunk-RNQHRDCQ.cjs +0 -16
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GENERATED FILE — do not edit by hand.
|
|
3
|
-
*
|
|
4
|
-
* Canonical algorithm: src/domain/arkOrderInvariants.ts
|
|
5
|
-
* Regenerate: node scripts/generate-cli-pure.mjs
|
|
6
|
-
* Drift check: node scripts/generate-cli-pure.mjs --check
|
|
7
|
-
*
|
|
8
|
-
* Pure CLI helper (bin/lib/ark-order-invariants.mjs). Zero Node I/O.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { ArkOrderError } from './ark-order-error.mjs';
|
|
12
|
-
import { CAPACITY_OPS, DEFAULT_MAX_XI_KEYS, XI_TTL_KEY_RE } from './ark-order-types.mjs';
|
|
13
|
-
import { deterministicHash, stableSerialize } from './stable-hash.mjs';
|
|
14
|
-
export { DEFAULT_MAX_XI_KEYS };
|
|
15
|
-
/** D7: consumer still owns handlers — this only names the travel verb. */
|
|
16
|
-
export function ingestTravelAction(residual) {
|
|
17
|
-
if (residual.kind === 'absorb')
|
|
18
|
-
return 'send';
|
|
19
|
-
if (residual.kind === 'escalate_up' && residual.target === 'human')
|
|
20
|
-
return 'raises';
|
|
21
|
-
return 'none';
|
|
22
|
-
}
|
|
23
|
-
const FORBIDDEN_PLANE_METHODS = ['update', 'patch', 'set', 'mutate'];
|
|
24
|
-
export function isForbiddenPlaneMethod(name) {
|
|
25
|
-
return FORBIDDEN_PLANE_METHODS.includes(name);
|
|
26
|
-
}
|
|
27
|
-
function isPrimitive(value) {
|
|
28
|
-
return (value === null ||
|
|
29
|
-
typeof value === 'string' ||
|
|
30
|
-
(typeof value === 'number' && Number.isFinite(value)) ||
|
|
31
|
-
typeof value === 'boolean');
|
|
32
|
-
}
|
|
33
|
-
export function freezeRecord(input, label) {
|
|
34
|
-
const keys = Object.keys(input);
|
|
35
|
-
if (keys.length === 0 && label === 'ξ') {
|
|
36
|
-
throw new ArkOrderError('ARKORDER_EMPTY_XI', 'ξ must name at least one slow mode');
|
|
37
|
-
}
|
|
38
|
-
const out = {};
|
|
39
|
-
for (const key of keys.sort()) {
|
|
40
|
-
const value = input[key];
|
|
41
|
-
if (typeof value === 'number' && !Number.isFinite(value)) {
|
|
42
|
-
// JSON (and so the hash) turns NaN/±Infinity into null: no stable identity.
|
|
43
|
-
throw new ArkOrderError('ARKORDER_NESTED_XI', `${label} key ${JSON.stringify(key)} is a non-finite number (NaN/±Infinity has no stable identity)`);
|
|
44
|
-
}
|
|
45
|
-
if (!isPrimitive(value)) {
|
|
46
|
-
throw new ArkOrderError('ARKORDER_NESTED_XI', `${label} key ${JSON.stringify(key)} is not a slow primitive (nested values are microstate)`);
|
|
47
|
-
}
|
|
48
|
-
out[key] = Object.is(value, -0) ? 0 : value;
|
|
49
|
-
}
|
|
50
|
-
return Object.freeze(out);
|
|
51
|
-
}
|
|
52
|
-
export function assertXiKeyCap(xi, maxXiKeys) {
|
|
53
|
-
const n = Object.keys(xi).length;
|
|
54
|
-
if (n === 0) {
|
|
55
|
-
throw new ArkOrderError('ARKORDER_EMPTY_XI', 'ξ must name at least one slow mode');
|
|
56
|
-
}
|
|
57
|
-
if (n > maxXiKeys) {
|
|
58
|
-
throw new ArkOrderError('ARKORDER_TOO_MANY_PARAMS', `ξ has ${n} keys; maxXiKeys is ${maxXiKeys} (Haken: few slow modes)`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
export function assertXiSchema(xi, schema) {
|
|
62
|
-
if (!schema)
|
|
63
|
-
return;
|
|
64
|
-
const properties = schema.properties ?? {};
|
|
65
|
-
const additional = schema.additionalProperties !== false ? true : false;
|
|
66
|
-
for (const key of Object.keys(xi)) {
|
|
67
|
-
const prop = properties[key];
|
|
68
|
-
if (!prop) {
|
|
69
|
-
if (!additional) {
|
|
70
|
-
throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} is not in xiSchema.properties`);
|
|
71
|
-
}
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (prop.enum && !prop.enum.some((allowed) => allowed === xi[key])) {
|
|
75
|
-
throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} value is not in enum`);
|
|
76
|
-
}
|
|
77
|
-
if (prop.type === 'null' && xi[key] !== null) {
|
|
78
|
-
throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} must be null`);
|
|
79
|
-
}
|
|
80
|
-
if (prop.type && prop.type !== 'null' && typeof xi[key] !== prop.type) {
|
|
81
|
-
throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} must be ${prop.type}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function catalogDigestFor(xi, catalogDigest) {
|
|
86
|
-
if (typeof catalogDigest !== 'string')
|
|
87
|
-
return undefined;
|
|
88
|
-
if (!Object.prototype.hasOwnProperty.call(xi, 'catalogReleaseId'))
|
|
89
|
-
return undefined;
|
|
90
|
-
return catalogDigest;
|
|
91
|
-
}
|
|
92
|
-
export function hashReleasePayload(xi, sigma, catalogDigest) {
|
|
93
|
-
const digest = catalogDigestFor(xi, catalogDigest);
|
|
94
|
-
if (digest !== undefined)
|
|
95
|
-
return deterministicHash(stableSerialize({ xi, sigma, catalogDigest: digest }));
|
|
96
|
-
return deterministicHash(stableSerialize({ xi, sigma }));
|
|
97
|
-
}
|
|
98
|
-
export function hashXiIdentity(xi, catalogDigest) {
|
|
99
|
-
const digest = catalogDigestFor(xi, catalogDigest);
|
|
100
|
-
if (digest !== undefined)
|
|
101
|
-
return deterministicHash(stableSerialize({ xi, catalogDigest: digest }));
|
|
102
|
-
return deterministicHash(stableSerialize({ xi }));
|
|
103
|
-
}
|
|
104
|
-
export function hashSigmaIdentity(sigma) {
|
|
105
|
-
return deterministicHash(stableSerialize({ sigma }));
|
|
106
|
-
}
|
|
107
|
-
export function xiRecordsEqual(left, right) {
|
|
108
|
-
return stableSerialize(left) === stableSerialize(right);
|
|
109
|
-
}
|
|
110
|
-
/** D1: after the first freeze, a later release() may not change ξ. */
|
|
111
|
-
export function assertUnvalvedRelease(current, nextXi) {
|
|
112
|
-
if (!current)
|
|
113
|
-
return;
|
|
114
|
-
if (xiRecordsEqual(current.xi, nextXi))
|
|
115
|
-
return;
|
|
116
|
-
throw new ArkOrderError('ARKORDER_UNVALVED_RELEASE', 'ξ is frozen; change the pattern with proposeRelease then apply(ProposeResult)');
|
|
117
|
-
}
|
|
118
|
-
export function createFrozenRelease(input) {
|
|
119
|
-
assertXiKeyCap(input.xi, input.maxXiKeys);
|
|
120
|
-
const xi = freezeRecord(input.xi, 'ξ');
|
|
121
|
-
assertXiHasNoTtl(xi);
|
|
122
|
-
assertXiSchema(xi, input.xiSchema);
|
|
123
|
-
const sigma = freezeRecord(input.sigma ?? {}, 'σ');
|
|
124
|
-
const release = Object.freeze({
|
|
125
|
-
version: input.version,
|
|
126
|
-
hash: hashReleasePayload(xi, sigma, input.catalogDigest),
|
|
127
|
-
xiHash: hashXiIdentity(xi, input.catalogDigest),
|
|
128
|
-
sigmaHash: hashSigmaIdentity(sigma),
|
|
129
|
-
xi,
|
|
130
|
-
sigma,
|
|
131
|
-
releasedAt: input.now,
|
|
132
|
-
});
|
|
133
|
-
return release;
|
|
134
|
-
}
|
|
135
|
-
/** D2: refresh σ without minting a pattern. xiHash must not change. */
|
|
136
|
-
export function refreshSigmaRecord(input) {
|
|
137
|
-
const sigma = freezeRecord(input.sigma, 'σ');
|
|
138
|
-
return Object.freeze({
|
|
139
|
-
version: input.current.version,
|
|
140
|
-
hash: hashReleasePayload(input.current.xi, sigma, input.catalogDigest),
|
|
141
|
-
xiHash: input.current.xiHash,
|
|
142
|
-
sigmaHash: hashSigmaIdentity(sigma),
|
|
143
|
-
xi: input.current.xi,
|
|
144
|
-
sigma,
|
|
145
|
-
releasedAt: input.now,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
export function assertXiHasNoTtl(xi) {
|
|
149
|
-
for (const key of Object.keys(xi)) {
|
|
150
|
-
if (XI_TTL_KEY_RE.test(key)) {
|
|
151
|
-
throw new ArkOrderError('ARKORDER_XI_TTL', `ξ key ${JSON.stringify(key)} is a freshness field; TTL belongs on σ, never on ξ`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
export function assertInformationBudget(projection, budget) {
|
|
156
|
-
if (!budget || budget.cannotObserve.length === 0)
|
|
157
|
-
return;
|
|
158
|
-
const denied = new Set(budget.cannotObserve);
|
|
159
|
-
for (const kind of projection.allowedKinds) {
|
|
160
|
-
if (denied.has(kind)) {
|
|
161
|
-
throw new ArkOrderError('ARKORDER_INFORMATION_BUDGET', `projection allows ${JSON.stringify(kind)}; informationBudget.cannotObserve forbids it`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* σ freshness. A numeric σ.freshUntil is honored on its own and wins over the age
|
|
167
|
-
* check; otherwise `maxAgeMs` (sigmaMaxAgeMs) bounds the age from σ.releasedAt or
|
|
168
|
-
* the Release freeze time. Neither set means σ never goes stale.
|
|
169
|
-
*/
|
|
170
|
-
export function assertSigmaFresh(input) {
|
|
171
|
-
const until = input.sigma.freshUntil;
|
|
172
|
-
if (typeof until === 'number') {
|
|
173
|
-
if (input.now > until) {
|
|
174
|
-
throw new ArkOrderError('ARKORDER_STALE_SIGMA', 'σ freshUntil has elapsed; ξ does not TTL');
|
|
175
|
-
}
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (input.maxAgeMs === undefined)
|
|
179
|
-
return;
|
|
180
|
-
const origin = typeof input.sigma.releasedAt === 'number' ? input.sigma.releasedAt : input.releasedAt;
|
|
181
|
-
if (typeof origin === 'number' && input.now - origin > input.maxAgeMs) {
|
|
182
|
-
throw new ArkOrderError('ARKORDER_STALE_SIGMA', 'σ is older than sigmaMaxAgeMs; ξ does not TTL');
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
export function fieldEventIdentity(event) {
|
|
186
|
-
return deterministicHash(stableSerialize({ kind: event.kind, payload: event.payload ?? null }));
|
|
187
|
-
}
|
|
188
|
-
function bindResidual(event, xiHash) {
|
|
189
|
-
return { event, xiHash, eventId: fieldEventIdentity(event) };
|
|
190
|
-
}
|
|
191
|
-
const CAPACITY_OP_SET = new Set(CAPACITY_OPS);
|
|
192
|
-
function isCapacityOp(value) {
|
|
193
|
-
return typeof value === 'string' && CAPACITY_OP_SET.has(value);
|
|
194
|
-
}
|
|
195
|
-
function numericLeaf(value) {
|
|
196
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
197
|
-
}
|
|
198
|
-
function compareCapacity(left, op, right) {
|
|
199
|
-
if (op === 'lte')
|
|
200
|
-
return left <= right;
|
|
201
|
-
if (op === 'lt')
|
|
202
|
-
return left < right;
|
|
203
|
-
if (op === 'gte')
|
|
204
|
-
return left >= right;
|
|
205
|
-
return left > right;
|
|
206
|
-
}
|
|
207
|
-
function packHasFunction(value) {
|
|
208
|
-
if (typeof value === 'function')
|
|
209
|
-
return true;
|
|
210
|
-
if (value === null || typeof value !== 'object')
|
|
211
|
-
return false;
|
|
212
|
-
if (Array.isArray(value))
|
|
213
|
-
return value.some(packHasFunction);
|
|
214
|
-
return Object.values(value).some(packHasFunction);
|
|
215
|
-
}
|
|
216
|
-
function evaluateCapacity(event, sigma, pack) {
|
|
217
|
-
const rows = pack.capacity ?? [];
|
|
218
|
-
for (const row of rows) {
|
|
219
|
-
if (packHasFunction(row) || !isCapacityOp(row.op))
|
|
220
|
-
return 'pack';
|
|
221
|
-
if (row.kind !== event.kind)
|
|
222
|
-
continue;
|
|
223
|
-
const payload = event.payload && typeof event.payload === 'object' && !Array.isArray(event.payload)
|
|
224
|
-
? numericLeaf(event.payload[row.payloadKey])
|
|
225
|
-
: undefined;
|
|
226
|
-
const limit = numericLeaf(sigma[row.sigmaKey]);
|
|
227
|
-
if (payload === undefined || limit === undefined)
|
|
228
|
-
return 'pack';
|
|
229
|
-
if (!compareCapacity(payload, row.op, limit))
|
|
230
|
-
return 'capacity';
|
|
231
|
-
}
|
|
232
|
-
return 'ok';
|
|
233
|
-
}
|
|
234
|
-
export function classifyIngest(projection, event, packs = [], xiHash = '', sigma = Object.freeze({})) {
|
|
235
|
-
const kind = event.kind;
|
|
236
|
-
const bound = bindResidual(event, xiHash);
|
|
237
|
-
for (const pack of packs) {
|
|
238
|
-
if (packHasFunction(pack.capacity) || packHasFunction(pack.escalateKinds)) {
|
|
239
|
-
return {
|
|
240
|
-
...bound,
|
|
241
|
-
kind: 'hold',
|
|
242
|
-
reasonCode: 'pack',
|
|
243
|
-
reason: `pack ${pack.id} is not data-only; user predicates are forbidden`,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
if (pack.escalateKinds?.includes(kind)) {
|
|
247
|
-
const target = pack.escalateTarget ?? 'human';
|
|
248
|
-
return {
|
|
249
|
-
...bound,
|
|
250
|
-
kind: 'escalate_up',
|
|
251
|
-
reasonCode: 'pack',
|
|
252
|
-
reason: `pack ${pack.id} slaves kind ${JSON.stringify(kind)} to a pattern change`,
|
|
253
|
-
target,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (!projection.allowedKinds.includes(kind)) {
|
|
258
|
-
return {
|
|
259
|
-
...bound,
|
|
260
|
-
kind: 'escalate_up',
|
|
261
|
-
reasonCode: 'not-in-pattern',
|
|
262
|
-
reason: `kind ${JSON.stringify(kind)} is not allowed by h(ξ); field cannot rewrite the pattern`,
|
|
263
|
-
target: 'human',
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
for (const pack of packs) {
|
|
267
|
-
const cap = evaluateCapacity(event, sigma, pack);
|
|
268
|
-
if (cap === 'ok')
|
|
269
|
-
continue;
|
|
270
|
-
return {
|
|
271
|
-
...bound,
|
|
272
|
-
kind: 'hold',
|
|
273
|
-
reasonCode: cap,
|
|
274
|
-
reason: cap === 'capacity'
|
|
275
|
-
? `pack ${pack.id} capacity ${JSON.stringify(event.kind)} does not hold`
|
|
276
|
-
: `pack ${pack.id} capacity is not numeric data`,
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
return { ...bound, kind: 'absorb' };
|
|
280
|
-
}
|
|
281
|
-
export function blastRadiusOf(previous, next) {
|
|
282
|
-
const prev = new Set(previous.allowedKinds);
|
|
283
|
-
const nxt = new Set(next.allowedKinds);
|
|
284
|
-
const blast = new Set();
|
|
285
|
-
for (const kind of prev) {
|
|
286
|
-
if (!nxt.has(kind))
|
|
287
|
-
blast.add(kind);
|
|
288
|
-
}
|
|
289
|
-
for (const kind of nxt) {
|
|
290
|
-
if (!prev.has(kind))
|
|
291
|
-
blast.add(kind);
|
|
292
|
-
}
|
|
293
|
-
for (const item of next.invalidated)
|
|
294
|
-
blast.add(item);
|
|
295
|
-
return {
|
|
296
|
-
blastRadius: [...blast].sort(),
|
|
297
|
-
invalidations: [...next.invalidated].sort(),
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
export function proposePatternChange(input) {
|
|
301
|
-
const merged = { ...input.current.xi };
|
|
302
|
-
for (const [key, value] of Object.entries(input.delta)) {
|
|
303
|
-
if (value === undefined) {
|
|
304
|
-
delete merged[key];
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
merged[key] = value;
|
|
308
|
-
}
|
|
309
|
-
const candidate = createFrozenRelease({
|
|
310
|
-
xi: merged,
|
|
311
|
-
sigma: { ...input.current.sigma },
|
|
312
|
-
version: input.current.version + 1,
|
|
313
|
-
now: input.now,
|
|
314
|
-
maxXiKeys: input.maxXiKeys,
|
|
315
|
-
xiSchema: input.xiSchema,
|
|
316
|
-
catalogDigest: input.catalogDigest,
|
|
317
|
-
});
|
|
318
|
-
if (candidate.hash === input.current.hash) {
|
|
319
|
-
throw new ArkOrderError('ARKORDER_EMPTY_BLAST', 'delta does not change ξ; that is not a pattern change');
|
|
320
|
-
}
|
|
321
|
-
const previous = input.projector(input.current, input.current.sigma);
|
|
322
|
-
const next = input.projector(candidate, candidate.sigma);
|
|
323
|
-
// A pattern the budget denies is never offered for review.
|
|
324
|
-
assertInformationBudget(next, input.informationBudget);
|
|
325
|
-
const { blastRadius, invalidations } = blastRadiusOf(previous, next);
|
|
326
|
-
if (blastRadius.length === 0) {
|
|
327
|
-
throw new ArkOrderError('ARKORDER_EMPTY_BLAST', 'pattern change has empty blast radius; that key is not an order parameter');
|
|
328
|
-
}
|
|
329
|
-
return {
|
|
330
|
-
nextXi: candidate.xi,
|
|
331
|
-
blastRadius,
|
|
332
|
-
invalidations,
|
|
333
|
-
baseXiHash: input.current.xiHash,
|
|
334
|
-
baseVersion: input.current.version,
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
function sameSortedList(left, right) {
|
|
338
|
-
if (!Array.isArray(left) || left.length !== right.length)
|
|
339
|
-
return false;
|
|
340
|
-
const sorted = [...left].map(String).sort();
|
|
341
|
-
return sorted.every((item, index) => item === right[index]);
|
|
342
|
-
}
|
|
343
|
-
function staleProposal(detail) {
|
|
344
|
-
return new ArkOrderError('ARKORDER_STALE_PROPOSAL', `${detail}; re-run proposeRelease against the current Release and review the new blast radius`);
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* D1 valve: freeze ProposeResult.nextXi. The proposal must be bound to the current
|
|
348
|
-
* Release (baseXiHash + baseVersion) and its reviewed blastRadius / invalidations must
|
|
349
|
-
* equal the transition actually committed — otherwise ARKORDER_STALE_PROPOSAL.
|
|
350
|
-
* Empty blast still fails; a pattern the information budget denies never persists.
|
|
351
|
-
*/
|
|
352
|
-
export function applyProposedRelease(input) {
|
|
353
|
-
const proposal = input.proposal;
|
|
354
|
-
if (!proposal ||
|
|
355
|
-
typeof proposal !== 'object' ||
|
|
356
|
-
!proposal.nextXi ||
|
|
357
|
-
typeof proposal.nextXi !== 'object') {
|
|
358
|
-
throw staleProposal('apply() requires a ProposeResult from proposeRelease');
|
|
359
|
-
}
|
|
360
|
-
if (proposal.baseXiHash !== input.current.xiHash ||
|
|
361
|
-
proposal.baseVersion !== input.current.version) {
|
|
362
|
-
throw staleProposal(`proposal was computed against a different Release (base v${String(proposal.baseVersion)}, current v${input.current.version})`);
|
|
363
|
-
}
|
|
364
|
-
const candidate = createFrozenRelease({
|
|
365
|
-
xi: { ...input.proposal.nextXi },
|
|
366
|
-
sigma: { ...input.current.sigma },
|
|
367
|
-
version: input.current.version + 1,
|
|
368
|
-
now: input.now,
|
|
369
|
-
maxXiKeys: input.maxXiKeys,
|
|
370
|
-
xiSchema: input.xiSchema,
|
|
371
|
-
catalogDigest: input.catalogDigest,
|
|
372
|
-
});
|
|
373
|
-
if (xiRecordsEqual(candidate.xi, input.current.xi)) {
|
|
374
|
-
throw new ArkOrderError('ARKORDER_EMPTY_BLAST', 'delta does not change ξ; that is not a pattern change');
|
|
375
|
-
}
|
|
376
|
-
const previous = input.projector(input.current, input.current.sigma);
|
|
377
|
-
const next = input.projector(candidate, candidate.sigma);
|
|
378
|
-
assertInformationBudget(next, input.informationBudget);
|
|
379
|
-
const { blastRadius, invalidations } = blastRadiusOf(previous, next);
|
|
380
|
-
if (blastRadius.length === 0) {
|
|
381
|
-
throw new ArkOrderError('ARKORDER_EMPTY_BLAST', 'pattern change has empty blast radius; that key is not an order parameter');
|
|
382
|
-
}
|
|
383
|
-
if (!sameSortedList(proposal.blastRadius, blastRadius) ||
|
|
384
|
-
!sameSortedList(proposal.invalidations, invalidations)) {
|
|
385
|
-
throw staleProposal('the reviewed blast radius is not the transition apply() would commit');
|
|
386
|
-
}
|
|
387
|
-
return candidate;
|
|
388
|
-
}
|
package/bin/lib/stable-hash.mjs
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GENERATED FILE — do not edit by hand.
|
|
3
|
-
*
|
|
4
|
-
* Canonical algorithm: src/domain/stableHash.ts
|
|
5
|
-
* Regenerate: node scripts/generate-cli-pure.mjs
|
|
6
|
-
* Drift check: node scripts/generate-cli-pure.mjs --check
|
|
7
|
-
*
|
|
8
|
-
* Pure CLI helper (bin/lib/stable-hash.mjs). Zero Node I/O.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/** Stable FNV-1a hash. Identity/fingerprint only — not a security primitive. */
|
|
12
|
-
export function deterministicHash(value) {
|
|
13
|
-
let hash = 0x811c9dc5;
|
|
14
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
15
|
-
hash ^= value.charCodeAt(index);
|
|
16
|
-
hash = Math.imul(hash, 0x01000193);
|
|
17
|
-
}
|
|
18
|
-
return `fnv1a-${(hash >>> 0).toString(16).padStart(8, '0')}`;
|
|
19
|
-
}
|
|
20
|
-
/** Serialize JSON-like values with sorted object keys for reproducible hashes. */
|
|
21
|
-
export function stableSerialize(value) {
|
|
22
|
-
if (value === null || typeof value !== 'object')
|
|
23
|
-
return JSON.stringify(value);
|
|
24
|
-
if (Array.isArray(value))
|
|
25
|
-
return `[${value.map(stableSerialize).join(',')}]`;
|
|
26
|
-
const object = value;
|
|
27
|
-
return `{${Object.keys(object)
|
|
28
|
-
.sort()
|
|
29
|
-
.map((key) => `${JSON.stringify(key)}:${stableSerialize(object[key])}`)
|
|
30
|
-
.join(',')}}`;
|
|
31
|
-
}
|
|
32
|
-
function feedText(text, state) {
|
|
33
|
-
let hash = state.hash;
|
|
34
|
-
for (let index = 0; index < text.length; index += 1) {
|
|
35
|
-
hash ^= text.charCodeAt(index);
|
|
36
|
-
hash = Math.imul(hash, 0x01000193);
|
|
37
|
-
}
|
|
38
|
-
state.hash = hash;
|
|
39
|
-
}
|
|
40
|
-
function feedStable(value, state) {
|
|
41
|
-
if (Array.isArray(value)) {
|
|
42
|
-
feedText('[', state);
|
|
43
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
44
|
-
if (index > 0)
|
|
45
|
-
feedText(',', state);
|
|
46
|
-
// `map` skips holes and `join` renders an undefined element as ''.
|
|
47
|
-
if (!(index in value))
|
|
48
|
-
continue;
|
|
49
|
-
const item = value[index];
|
|
50
|
-
if (item !== null && typeof item === 'object')
|
|
51
|
-
feedStable(item, state);
|
|
52
|
-
else {
|
|
53
|
-
const text = JSON.stringify(item);
|
|
54
|
-
if (text !== undefined)
|
|
55
|
-
feedText(text, state);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
feedText(']', state);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const object = value;
|
|
62
|
-
const keys = Object.keys(object).sort();
|
|
63
|
-
feedText('{', state);
|
|
64
|
-
for (let index = 0; index < keys.length; index += 1) {
|
|
65
|
-
if (index > 0)
|
|
66
|
-
feedText(',', state);
|
|
67
|
-
const key = keys[index];
|
|
68
|
-
feedText(`${JSON.stringify(key)}:`, state);
|
|
69
|
-
const item = object[key];
|
|
70
|
-
// A template literal renders an undefined serialization as 'undefined'.
|
|
71
|
-
if (item !== null && typeof item === 'object')
|
|
72
|
-
feedStable(item, state);
|
|
73
|
-
else
|
|
74
|
-
feedText(`${JSON.stringify(item)}`, state);
|
|
75
|
-
}
|
|
76
|
-
feedText('}', state);
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* `deterministicHash(stableSerialize(value))`, byte for byte, without building
|
|
80
|
-
* the serialized text. A large fact set hashes without first materializing a
|
|
81
|
-
* multi-megabyte string and every intermediate join.
|
|
82
|
-
*/
|
|
83
|
-
export function stableHash(value) {
|
|
84
|
-
if (value === null || typeof value !== 'object') {
|
|
85
|
-
return deterministicHash(stableSerialize(value));
|
|
86
|
-
}
|
|
87
|
-
const state = { hash: 0x811c9dc5 };
|
|
88
|
-
feedStable(value, state);
|
|
89
|
-
return `fnv1a-${(state.hash >>> 0).toString(16).padStart(8, '0')}`;
|
|
90
|
-
}
|