@ultimat3/manifest 19.2.0 → 19.3.1
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/CLAUDE.md +9 -0
- package/package.json +6 -6
- package/src/diff-operations.ts +30 -1
package/CLAUDE.md
CHANGED
|
@@ -113,6 +113,15 @@ by the CLI, not imported.
|
|
|
113
113
|
route's `surface`/`offline`/`hydrate`, and a query's `cacheTags` — and every one of them
|
|
114
114
|
reported exactly `[{ kind: 'internal', path: 'buildId' }]` with `hasBreaking: false`. A new
|
|
115
115
|
SECTION needs its own file beside the others; a new FIELD joins the file its section owns.
|
|
116
|
+
- **`ActionFact.mutator` is classified, and LOSING it is breaking** (`As of 2026-09`). It was
|
|
117
|
+
written by `sources.ts` and read by no `diff-*.ts` rule — two manifests differing only in an
|
|
118
|
+
action that stopped being a mutator answered `[{ kind: 'internal', path: 'buildId' }]`, the exact
|
|
119
|
+
failure the rule above exists for. A mutator is a client-contract CAPABILITY (it decides the HTTP
|
|
120
|
+
method and the idempotency the typed client and OpenAPI publish), so losing it refuses callers
|
|
121
|
+
written against it and gaining it refuses nobody. Folded with `=== true` rather than compared
|
|
122
|
+
through `diffScalar`: `sources.ts` writes the field only when true, so absence IS `false` here,
|
|
123
|
+
and an older manifest can therefore only under-report a mutator as newly GAINED, which is
|
|
124
|
+
additive. `mcp.description` joins it as `internal` — visible in the file, in no contract.
|
|
116
125
|
- **The axis is what a change refuses, not how it reads.** Something that rejects input that was
|
|
117
126
|
valid yesterday is breaking (an invariant added, a NOT NULL, a gained permission, a gained
|
|
118
127
|
enforcement site, a lowered `retry.attempts`); something that accepts more is additive and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/manifest",
|
|
3
|
-
"version": "19.
|
|
3
|
+
"version": "19.3.1",
|
|
4
4
|
"description": "x.manifest.json: deterministic generated facts, contract diff, AGENTS.md budget",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"test": "bun test"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ultimat3/action": "19.
|
|
35
|
-
"@ultimat3/core": "19.
|
|
36
|
-
"@ultimat3/entity": "19.
|
|
37
|
-
"@ultimat3/jobs": "19.
|
|
38
|
-
"@ultimat3/query": "19.
|
|
34
|
+
"@ultimat3/action": "19.3.1",
|
|
35
|
+
"@ultimat3/core": "19.3.1",
|
|
36
|
+
"@ultimat3/entity": "19.3.1",
|
|
37
|
+
"@ultimat3/jobs": "19.3.1",
|
|
38
|
+
"@ultimat3/query": "19.3.1"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/src/diff-operations.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { canonicalJson, isMcpExposed } from '@ultimat3/core';
|
|
4
4
|
import type { ManifestChange } from './diff-change';
|
|
5
|
-
import { index } from './diff-change';
|
|
5
|
+
import { diffScalar, index } from './diff-change';
|
|
6
6
|
import { diffRateLimit } from './diff-rate-limit';
|
|
7
7
|
import type { ActionFact, QueryFact } from './schema';
|
|
8
8
|
|
|
@@ -49,6 +49,35 @@ export function diffActions(
|
|
|
49
49
|
detail: `mcp exposure ${String(exposed)} -> ${String(nextExposed)}`,
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
+
// `?.`, because `before` is a file parsed off DISK: a hand-trimmed or older manifest can carry
|
|
53
|
+
// no `mcp` block at all, and a description is not worth a throw out of the differ.
|
|
54
|
+
changes.push(
|
|
55
|
+
...diffScalar(
|
|
56
|
+
'internal',
|
|
57
|
+
`${path}.mcp.description`,
|
|
58
|
+
action.mcp?.description,
|
|
59
|
+
next.mcp?.description,
|
|
60
|
+
() => 'description changed',
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
// Written only when TRUE (`sources.ts`), so absence IS `false` here — a fold, the way
|
|
64
|
+
// `isMcpExposed` folds `expose`, and deliberately not `diffScalar`'s "absence is no evidence":
|
|
65
|
+
// the value this field is absent FOR is the value it means. A manifest written before the
|
|
66
|
+
// field existed can therefore only under-report a mutator as newly gained, which is additive
|
|
67
|
+
// and not a wall of false breakings.
|
|
68
|
+
//
|
|
69
|
+
// The direction is the decision: a mutator is a client-contract CAPABILITY, not a label — it
|
|
70
|
+
// decides the HTTP method and the idempotency the typed client and the OpenAPI document
|
|
71
|
+
// publish — so an action that stops being one refuses callers written against it.
|
|
72
|
+
const mutator = action.mutator === true;
|
|
73
|
+
const nextMutator = next.mutator === true;
|
|
74
|
+
if (mutator !== nextMutator) {
|
|
75
|
+
changes.push({
|
|
76
|
+
kind: nextMutator ? 'additive' : 'breaking',
|
|
77
|
+
path: `${path}.mutator`,
|
|
78
|
+
detail: `mutator ${String(mutator)} -> ${String(nextMutator)}`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
52
81
|
changes.push(...diffPermissions(path, action, next));
|
|
53
82
|
changes.push(...diffRateLimit(path, action, next));
|
|
54
83
|
if (canonicalJson(action.cacheInvalidates) !== canonicalJson(next.cacheInvalidates)) {
|