@anatomytool/validate 1.0.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/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/canonical.d.ts +21 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +76 -0
- package/dist/canonical.js.map +1 -0
- package/dist/checks/commands-no-test-warn.d.ts +6 -0
- package/dist/checks/commands-no-test-warn.d.ts.map +1 -0
- package/dist/checks/commands-no-test-warn.js +35 -0
- package/dist/checks/commands-no-test-warn.js.map +1 -0
- package/dist/checks/description-warn.d.ts +6 -0
- package/dist/checks/description-warn.d.ts.map +1 -0
- package/dist/checks/description-warn.js +23 -0
- package/dist/checks/description-warn.js.map +1 -0
- package/dist/checks/entry-point-alias-warn.d.ts +6 -0
- package/dist/checks/entry-point-alias-warn.d.ts.map +1 -0
- package/dist/checks/entry-point-alias-warn.js +25 -0
- package/dist/checks/entry-point-alias-warn.js.map +1 -0
- package/dist/checks/fingerprint-check.d.ts +6 -0
- package/dist/checks/fingerprint-check.d.ts.map +1 -0
- package/dist/checks/fingerprint-check.js +59 -0
- package/dist/checks/fingerprint-check.js.map +1 -0
- package/dist/checks/hash-check.d.ts +6 -0
- package/dist/checks/hash-check.d.ts.map +1 -0
- package/dist/checks/hash-check.js +36 -0
- package/dist/checks/hash-check.js.map +1 -0
- package/dist/checks/interface-form-check.d.ts +6 -0
- package/dist/checks/interface-form-check.d.ts.map +1 -0
- package/dist/checks/interface-form-check.js +66 -0
- package/dist/checks/interface-form-check.js.map +1 -0
- package/dist/checks/memory-dangling-ref-check.d.ts +6 -0
- package/dist/checks/memory-dangling-ref-check.d.ts.map +1 -0
- package/dist/checks/memory-dangling-ref-check.js +34 -0
- package/dist/checks/memory-dangling-ref-check.js.map +1 -0
- package/dist/checks/memory-fingerprint-check.d.ts +6 -0
- package/dist/checks/memory-fingerprint-check.d.ts.map +1 -0
- package/dist/checks/memory-fingerprint-check.js +20 -0
- package/dist/checks/memory-fingerprint-check.js.map +1 -0
- package/dist/checks/memory-supersession-check.d.ts +6 -0
- package/dist/checks/memory-supersession-check.d.ts.map +1 -0
- package/dist/checks/memory-supersession-check.js +61 -0
- package/dist/checks/memory-supersession-check.js.map +1 -0
- package/dist/checks/memory-verification-check.d.ts +6 -0
- package/dist/checks/memory-verification-check.d.ts.map +1 -0
- package/dist/checks/memory-verification-check.js +61 -0
- package/dist/checks/memory-verification-check.js.map +1 -0
- package/dist/checks/nested-path-escape.d.ts +6 -0
- package/dist/checks/nested-path-escape.d.ts.map +1 -0
- package/dist/checks/nested-path-escape.js +76 -0
- package/dist/checks/nested-path-escape.js.map +1 -0
- package/dist/checks/schema-check.d.ts +6 -0
- package/dist/checks/schema-check.d.ts.map +1 -0
- package/dist/checks/schema-check.js +32 -0
- package/dist/checks/schema-check.js.map +1 -0
- package/dist/checks/source-cross-check.d.ts +52 -0
- package/dist/checks/source-cross-check.d.ts.map +1 -0
- package/dist/checks/source-cross-check.js +473 -0
- package/dist/checks/source-cross-check.js.map +1 -0
- package/dist/checks/source-path-check.d.ts +6 -0
- package/dist/checks/source-path-check.d.ts.map +1 -0
- package/dist/checks/source-path-check.js +62 -0
- package/dist/checks/source-path-check.js.map +1 -0
- package/dist/checks/structure-path-check.d.ts +6 -0
- package/dist/checks/structure-path-check.d.ts.map +1 -0
- package/dist/checks/structure-path-check.js +31 -0
- package/dist/checks/structure-path-check.js.map +1 -0
- package/dist/checks/verify/ast-grep-verifier.d.ts +4 -0
- package/dist/checks/verify/ast-grep-verifier.d.ts.map +1 -0
- package/dist/checks/verify/ast-grep-verifier.js +153 -0
- package/dist/checks/verify/ast-grep-verifier.js.map +1 -0
- package/dist/checks/verify/detect-ast-grep.d.ts +9 -0
- package/dist/checks/verify/detect-ast-grep.d.ts.map +1 -0
- package/dist/checks/verify/detect-ast-grep.js +26 -0
- package/dist/checks/verify/detect-ast-grep.js.map +1 -0
- package/dist/checks/verify/detect-semgrep.d.ts +8 -0
- package/dist/checks/verify/detect-semgrep.d.ts.map +1 -0
- package/dist/checks/verify/detect-semgrep.js +36 -0
- package/dist/checks/verify/detect-semgrep.js.map +1 -0
- package/dist/checks/verify/glob-verifier.d.ts +5 -0
- package/dist/checks/verify/glob-verifier.d.ts.map +1 -0
- package/dist/checks/verify/glob-verifier.js +58 -0
- package/dist/checks/verify/glob-verifier.js.map +1 -0
- package/dist/checks/verify/semgrep-pattern-verifier.d.ts +4 -0
- package/dist/checks/verify/semgrep-pattern-verifier.d.ts.map +1 -0
- package/dist/checks/verify/semgrep-pattern-verifier.js +121 -0
- package/dist/checks/verify/semgrep-pattern-verifier.js.map +1 -0
- package/dist/checks/verify/semgrep-rule-file-verifier.d.ts +9 -0
- package/dist/checks/verify/semgrep-rule-file-verifier.d.ts.map +1 -0
- package/dist/checks/verify/semgrep-rule-file-verifier.js +151 -0
- package/dist/checks/verify/semgrep-rule-file-verifier.js.map +1 -0
- package/dist/checks/verify/types.d.ts +44 -0
- package/dist/checks/verify/types.d.ts.map +1 -0
- package/dist/checks/verify/types.js +6 -0
- package/dist/checks/verify/types.js.map +1 -0
- package/dist/checks/verify-check.d.ts +9 -0
- package/dist/checks/verify-check.d.ts.map +1 -0
- package/dist/checks/verify-check.js +67 -0
- package/dist/checks/verify-check.js.map +1 -0
- package/dist/discovery.d.ts +13 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +82 -0
- package/dist/discovery.js.map +1 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +5 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/memory.d.ts +19 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +80 -0
- package/dist/memory.js.map +1 -0
- package/dist/parse.d.ts +17 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +47 -0
- package/dist/parse.js.map +1 -0
- package/dist/schema-0.10.json +194 -0
- package/dist/schema-0.11.json +203 -0
- package/dist/schema-0.12.json +243 -0
- package/dist/schema-0.13.json +267 -0
- package/dist/schema-0.14.json +288 -0
- package/dist/schema-0.15.json +347 -0
- package/dist/schema-0.2.json +283 -0
- package/dist/schema-0.4.json +280 -0
- package/dist/schema-0.5.json +300 -0
- package/dist/schema-0.6.json +328 -0
- package/dist/schema-0.7.json +311 -0
- package/dist/schema-0.8.json +245 -0
- package/dist/schema-0.9.json +172 -0
- package/dist/schema-1.0.json +347 -0
- package/dist/schema-memory-0.1.json +43 -0
- package/dist/schema-memory-0.2.json +49 -0
- package/dist/schema-memory.d.ts +5 -0
- package/dist/schema-memory.d.ts.map +1 -0
- package/dist/schema-memory.js +16 -0
- package/dist/schema-memory.js.map +1 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +62 -0
- package/dist/schema.js.map +1 -0
- package/dist/schema.json +129 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.generated.d.ts +1103 -0
- package/dist/types.generated.d.ts.map +1 -0
- package/dist/types.generated.js +7 -0
- package/dist/types.generated.js.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/validate-tree.d.ts +20 -0
- package/dist/validate-tree.d.ts.map +1 -0
- package/dist/validate-tree.js +64 -0
- package/dist/validate-tree.js.map +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The Anatomy Standard contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# `@anatomy/validate`
|
|
2
|
+
|
|
3
|
+
TypeScript validator for `.anatomy` and `.anatomy-memory` files. Routes by declared wire version (v0.1, v0.2, v0.4, v0.5, v0.6, v0.7, v0.8) and supports v0.3 cascading semantics for repos with multiple `.anatomy` files.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @anatomy/validate
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js ≥ 22.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Single file
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { validate } from "@anatomy/validate";
|
|
19
|
+
import { readFileSync } from "node:fs";
|
|
20
|
+
|
|
21
|
+
const text = readFileSync(".anatomy", "utf8");
|
|
22
|
+
const result = validate(text, { repoRoot: process.cwd() });
|
|
23
|
+
|
|
24
|
+
if (result.ok) {
|
|
25
|
+
console.log("Valid:", result.value.identity);
|
|
26
|
+
for (const w of result.warnings) console.warn(w.code, w.message);
|
|
27
|
+
} else {
|
|
28
|
+
for (const e of result.errors) console.error(e.code, e.pointer, e.message);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Cascading tree
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { validateTree } from "@anatomy/validate";
|
|
36
|
+
|
|
37
|
+
const tree = validateTree(repoRoot);
|
|
38
|
+
// tree.results: Map<path, ValidateResult>
|
|
39
|
+
// tree.crossFile: warnings spanning multiple .anatomy files (e.g. duplicate-fingerprint-in-tree)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Memory file
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { validateMemory } from "@anatomy/validate";
|
|
46
|
+
|
|
47
|
+
const result = validateMemory(memoryText, {
|
|
48
|
+
anatomyFingerprint: parsedAnatomy.identity.fingerprint,
|
|
49
|
+
repoRoot,
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## What it checks
|
|
54
|
+
|
|
55
|
+
### `.anatomy` (single file)
|
|
56
|
+
|
|
57
|
+
- **TOML syntax** — parse error → `toml-parse` error.
|
|
58
|
+
- **Schema conformance** — version-routed against `spec/{0.1, 0.2, 0.4, 0.5, 0.6, 0.7, 0.8}/schema.json`. Unknown `anatomy_version` → `unsupported-anatomy-version`.
|
|
59
|
+
- **Identity integrity** — version-aware:
|
|
60
|
+
- **v0.7+ (incl. v0.8):** flat 4-string identity + single fingerprint via `fingerprintFromPillars(stack, form, domain, function)` = `Crockford-base32(SHA-256(stack\0form\0domain\0function))[:20]`.
|
|
61
|
+
- **v0.1–v0.6:** per-pillar `hash = canonicalHash(id)`; `fingerprint = concat(stack.hash, form.hash, domain.hash, function.hash)`.
|
|
62
|
+
- **Path checks** — `structure.entries[].path`, `entry_points[].path`, `phrase_with_source.source.{path, symbol}` — soft-warn if missing on disk; nested-path-escape error for paths that climb above `repoRoot/anatomyDir`.
|
|
63
|
+
- **Interface↔form match** — `[interface.exports]` requires a library-shaped form; `[interface.subcommands]` requires a CLI-shaped form; etc.
|
|
64
|
+
- **Soft warnings** — `description-too-long`, `entry-point-description-deprecated` (v0.2 alias), `commands-no-test` (v0.4+: `[operation.commands]` without a `test` key).
|
|
65
|
+
|
|
66
|
+
### `.anatomy-memory` (paired file)
|
|
67
|
+
|
|
68
|
+
- **Schema conformance** — version-routed against `spec/memory/{0.1, 0.2}/schema.json`.
|
|
69
|
+
- **Paired-fingerprint integrity** — `repo_fingerprint` must match the paired `.anatomy`'s fingerprint (`memory-fingerprint-mismatch`).
|
|
70
|
+
- **Supersession integrity** — no cycles (`memory-supersedes-cycle`), no dangling targets (`memory-supersedes-not-found`).
|
|
71
|
+
- **Reference soundness** — entry `refs` pointing to nonexistent files → `memory-dangling-ref` warning.
|
|
72
|
+
- **v0.2 verification field hygiene** — `verified_by` items match the attribution regex (`memory-verified-by-malformed`); `verified_by` array bounded at 5 (`memory-verified-by-too-many` warning if exceeded by hand-edits); `last_verified_at` not earlier than the entry's creation `at` (`memory-last-verified-before-at` warning).
|
|
73
|
+
|
|
74
|
+
### Rule verification (v0.12+)
|
|
75
|
+
|
|
76
|
+
Each `[[rules]]` entry may carry an optional `verify` field that declares how to check the rule against actual source. Three kinds:
|
|
77
|
+
|
|
78
|
+
- `glob_exists` — assert files matching a glob exist (or, with `should_not=true`, don't exist).
|
|
79
|
+
- `glob_only` — assert files matching one glob all live inside another.
|
|
80
|
+
- `ast_pattern` — ast-grep pattern + `expect_in` or `forbid_in` glob. Requires the optional `@ast-grep/napi` dependency.
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
|
|
84
|
+
```toml
|
|
85
|
+
[[rules]]
|
|
86
|
+
rule = "Tests live in tests/"
|
|
87
|
+
verify = { kind = "glob_exists", path = "tests/*.test.ts" }
|
|
88
|
+
|
|
89
|
+
[[rules]]
|
|
90
|
+
rule = "No fetch() outside src/api/"
|
|
91
|
+
verify = { kind = "ast_pattern", lang = "ts", pattern = "fetch($_)", forbid_in = "src/!(api)/**/*.ts" }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Verify clauses run during `validate()` when `repoRoot` is provided. Violations surface as warnings; under `anatomy validate --strict` (the default), the relevant warning codes elevate to errors.
|
|
95
|
+
|
|
96
|
+
**`validate()` is async as of v0.12.** Earlier versions returned a sync object; callers must now `await`.
|
|
97
|
+
|
|
98
|
+
## Cascading (v0.3 ecosystem)
|
|
99
|
+
|
|
100
|
+
For repos with multiple `.anatomy` files (one at root, plus per-package overrides):
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { findAnatomyForPath, discoverAllAnatomies } from "@anatomy/validate";
|
|
104
|
+
|
|
105
|
+
const nearest = findAnatomyForPath(repoRoot, "packages/server/src/index.ts");
|
|
106
|
+
const all = discoverAllAnatomies(repoRoot);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Cross-file checks include `duplicate-fingerprint-in-tree` (sibling files sharing a fingerprint).
|
|
110
|
+
|
|
111
|
+
## Public API
|
|
112
|
+
|
|
113
|
+
| Export | What |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `validate(text, options)` | Single-file validation; returns `{ ok, value?, errors, warnings }`. |
|
|
116
|
+
| `validateTree(repoRoot, options)` | Cascading tree validation. |
|
|
117
|
+
| `validateMemory(text, options)` | `.anatomy-memory` single-file validation. (No tree-mode equivalent yet — call validateMemory once per discovered memory file.) |
|
|
118
|
+
| `findAnatomyForPath(repoRoot, queryPath)` | Locate the nearest `.anatomy` for a given file path. |
|
|
119
|
+
| `discoverAllAnatomies(repoRoot, options)` | Walk and parse every `.anatomy` in a tree. |
|
|
120
|
+
| `canonicalize(s)` / `hash(c)` / `canonicalHash(s)` / `fingerprintFromPillars(stack, form, domain, fn)` | Re-exported canonical-form helpers. |
|
|
121
|
+
| `supportedVersions` | Frozen tuple of supported wire versions. |
|
|
122
|
+
| `ECOSYSTEM_VERSION` | Currently `"0.3"`. |
|
|
123
|
+
| Types: `AnatomyDoc`, `ValidationError`, `Warning`, `ValidateOptions`, `ValidateResult`, `ErrorCode`, `WarningCode` |
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reduce input to canonical form per spec/0.1/canonicalization.md.
|
|
3
|
+
* Returns null if the input fails alphabet validation or reduces to empty.
|
|
4
|
+
*/
|
|
5
|
+
export declare function canonicalize(input: unknown): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Compute a 5-char lowercase Crockford-base32 hash from canonical form.
|
|
8
|
+
* Throws if the input is not already in canonical form.
|
|
9
|
+
*/
|
|
10
|
+
export declare function hash(canonical: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Convenience: canonicalize then hash. Returns null if canonicalization fails.
|
|
13
|
+
*/
|
|
14
|
+
export declare function canonicalHash(input: unknown): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Compute the v0.7 identity fingerprint from four canonical pillar ID strings.
|
|
17
|
+
* Formula: first 20 Crockford-base32 chars of SHA-256(stack NUL form NUL domain NUL function).
|
|
18
|
+
* Inputs must already be in canonical form (lowercase hyphenated). Does NOT validate them.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fingerprintFromPillars(stack: string, form: string, domain: string, fn: string): string;
|
|
21
|
+
//# sourceMappingURL=canonical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAQ1D;AAsBD;;;GAGG;AACH,wBAAgB,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI3D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GACtD,MAAM,CAKR"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/canonical.ts
|
|
2
|
+
// Implements the normative canonicalization algorithm from spec/0.1/canonicalization.md
|
|
3
|
+
// and the v0.7 fingerprint formula. Logic must stay byte-identical with the two
|
|
4
|
+
// other copies in this monorepo:
|
|
5
|
+
// - scripts/canonical.mjs (the spec's normative reference implementation)
|
|
6
|
+
// - anatomy-cli/src/canonical.ts (the CLI's copy)
|
|
7
|
+
// fingerprintFromPillars originated in anatomy-cli and was mirrored here and to
|
|
8
|
+
// scripts/; the canonicalize/hash/canonicalHash trio has not changed since v0.1.
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
const ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz"; // Crockford-base32, lowercase
|
|
11
|
+
/**
|
|
12
|
+
* Reduce input to canonical form per spec/0.1/canonicalization.md.
|
|
13
|
+
* Returns null if the input fails alphabet validation or reduces to empty.
|
|
14
|
+
*/
|
|
15
|
+
export function canonicalize(input) {
|
|
16
|
+
if (typeof input !== "string")
|
|
17
|
+
return null;
|
|
18
|
+
let s = input.toLowerCase();
|
|
19
|
+
s = s.replace(/[\s_]+/g, "-");
|
|
20
|
+
s = s.replace(/^[-.,:;]+|[-.,:;]+$/g, "");
|
|
21
|
+
s = s.replace(/-+/g, "-");
|
|
22
|
+
if (s === "" || /[^a-z0-9-]/.test(s))
|
|
23
|
+
return null;
|
|
24
|
+
return s;
|
|
25
|
+
}
|
|
26
|
+
function toBase32(bytes) {
|
|
27
|
+
let bits = 0n;
|
|
28
|
+
let bitCount = 0;
|
|
29
|
+
for (const b of bytes) {
|
|
30
|
+
bits = (bits << 8n) | BigInt(b);
|
|
31
|
+
bitCount += 8;
|
|
32
|
+
}
|
|
33
|
+
let out = "";
|
|
34
|
+
while (bitCount >= 5) {
|
|
35
|
+
bitCount -= 5;
|
|
36
|
+
const idx = Number((bits >> BigInt(bitCount)) & 0x1fn);
|
|
37
|
+
out += ALPHABET[idx];
|
|
38
|
+
}
|
|
39
|
+
if (bitCount > 0) {
|
|
40
|
+
const idx = Number((bits << BigInt(5 - bitCount)) & 0x1fn);
|
|
41
|
+
out += ALPHABET[idx];
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Compute a 5-char lowercase Crockford-base32 hash from canonical form.
|
|
47
|
+
* Throws if the input is not already in canonical form.
|
|
48
|
+
*/
|
|
49
|
+
export function hash(canonical) {
|
|
50
|
+
if (canonicalize(canonical) !== canonical) {
|
|
51
|
+
throw new Error(`hash() called on non-canonical input: ${JSON.stringify(canonical)}`);
|
|
52
|
+
}
|
|
53
|
+
const digest = createHash("sha256").update(canonical, "utf8").digest();
|
|
54
|
+
return toBase32(digest).slice(0, 5);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Convenience: canonicalize then hash. Returns null if canonicalization fails.
|
|
58
|
+
*/
|
|
59
|
+
export function canonicalHash(input) {
|
|
60
|
+
const c = canonicalize(input);
|
|
61
|
+
if (c === null)
|
|
62
|
+
return null;
|
|
63
|
+
return hash(c);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Compute the v0.7 identity fingerprint from four canonical pillar ID strings.
|
|
67
|
+
* Formula: first 20 Crockford-base32 chars of SHA-256(stack NUL form NUL domain NUL function).
|
|
68
|
+
* Inputs must already be in canonical form (lowercase hyphenated). Does NOT validate them.
|
|
69
|
+
*/
|
|
70
|
+
export function fingerprintFromPillars(stack, form, domain, fn) {
|
|
71
|
+
const digest = createHash("sha256")
|
|
72
|
+
.update(`${stack}\x00${form}\x00${domain}\x00${fn}`, "utf8")
|
|
73
|
+
.digest();
|
|
74
|
+
return toBase32(digest).slice(0, 20);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=canonical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands-no-test-warn.d.ts","sourceRoot":"","sources":["../../src/checks/commands-no-test-warn.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAI7D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG;IAChD,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CA4BA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/checks/commands-no-test-warn.ts
|
|
2
|
+
// Validator-side warning: a v0.4+ doc declares [operation.commands] but the
|
|
3
|
+
// table has no key named "test". `test` is the de facto standard key for
|
|
4
|
+
// running the project's test suite; missing it is a soft signal that the
|
|
5
|
+
// commands table is incomplete. v0.1/v0.2/v0.3 docs are skipped — the
|
|
6
|
+
// recommendation only applies once operation.commands became a v0.4 norm.
|
|
7
|
+
const APPLICABLE_VERSIONS = new Set(["0.4", "0.5", "0.6", "0.7", "0.8"]);
|
|
8
|
+
export function commandsNoTestWarn(doc) {
|
|
9
|
+
const d = doc;
|
|
10
|
+
if (typeof d?.anatomy_version !== "string")
|
|
11
|
+
return { errors: [], warnings: [] };
|
|
12
|
+
if (!APPLICABLE_VERSIONS.has(d.anatomy_version))
|
|
13
|
+
return { errors: [], warnings: [] };
|
|
14
|
+
const commands = d?.operation?.commands;
|
|
15
|
+
// Only fire when the table is actually present (a non-null object). An
|
|
16
|
+
// empty/non-object commands value is left to the schema check.
|
|
17
|
+
if (commands === null || typeof commands !== "object")
|
|
18
|
+
return { errors: [], warnings: [] };
|
|
19
|
+
const keys = Object.keys(commands);
|
|
20
|
+
if (keys.length === 0)
|
|
21
|
+
return { errors: [], warnings: [] };
|
|
22
|
+
if (keys.includes("test"))
|
|
23
|
+
return { errors: [], warnings: [] };
|
|
24
|
+
return {
|
|
25
|
+
errors: [],
|
|
26
|
+
warnings: [
|
|
27
|
+
{
|
|
28
|
+
code: "commands-no-test",
|
|
29
|
+
message: "[operation.commands] does not declare a 'test' key; consider adding one to document how to run the test suite",
|
|
30
|
+
pointer: "/operation/commands",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=commands-no-test-warn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands-no-test-warn.js","sourceRoot":"","sources":["../../src/checks/commands-no-test-warn.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,4EAA4E;AAC5E,yEAAyE;AACzE,yEAAyE;AACzE,sEAAsE;AACtE,0EAA0E;AAI1E,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAEzE,MAAM,UAAU,kBAAkB,CAAC,GAAY;IAI7C,MAAM,CAAC,GAAG,GAGT,CAAC;IAEF,IAAI,OAAO,CAAC,EAAE,eAAe,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAChF,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,eAAe,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAErF,MAAM,QAAQ,GAAG,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC;IACxC,uEAAuE;IACvE,+DAA+D;IAC/D,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAE3F,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,QAAmC,CAAC,CAAC;IAC9D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC3D,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAE/D,OAAO;QACL,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,kBAAkB;gBACxB,OAAO,EAAE,+GAA+G;gBACxH,OAAO,EAAE,qBAAqB;aAC/B;SACF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"description-warn.d.ts","sourceRoot":"","sources":["../../src/checks/description-warn.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAI7D,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG;IAClD,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CAeA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/checks/description-warn.ts
|
|
2
|
+
// Validator-side warning: description > 500 characters. Schema does NOT
|
|
3
|
+
// enforce this (a maxLength constraint would be an error, not a warning;
|
|
4
|
+
// JSON Schema can't model warnings).
|
|
5
|
+
const MAX_DESCRIPTION_LENGTH = 500;
|
|
6
|
+
export function descriptionWarnCheck(doc) {
|
|
7
|
+
const desc = doc?.description;
|
|
8
|
+
if (typeof desc !== "string")
|
|
9
|
+
return { errors: [], warnings: [] };
|
|
10
|
+
if (desc.length <= MAX_DESCRIPTION_LENGTH)
|
|
11
|
+
return { errors: [], warnings: [] };
|
|
12
|
+
return {
|
|
13
|
+
errors: [],
|
|
14
|
+
warnings: [
|
|
15
|
+
{
|
|
16
|
+
code: "description-too-long",
|
|
17
|
+
message: `description exceeds ${MAX_DESCRIPTION_LENGTH} characters (actual: ${desc.length})`,
|
|
18
|
+
pointer: "/description",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=description-warn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"description-warn.js","sourceRoot":"","sources":["../../src/checks/description-warn.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,wEAAwE;AACxE,yEAAyE;AACzE,qCAAqC;AAIrC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAEnC,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAI/C,MAAM,IAAI,GAAI,GAAiC,EAAE,WAAW,CAAC;IAC7D,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAClE,IAAI,IAAI,CAAC,MAAM,IAAI,sBAAsB;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAE/E,OAAO;QACL,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,sBAAsB;gBAC5B,OAAO,EAAE,uBAAuB,sBAAsB,wBAAwB,IAAI,CAAC,MAAM,GAAG;gBAC5F,OAAO,EAAE,cAAc;aACxB;SACF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-point-alias-warn.d.ts","sourceRoot":"","sources":["../../src/checks/entry-point-alias-warn.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE7D,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG;IACjD,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CAsBA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/checks/entry-point-alias-warn.ts
|
|
2
|
+
// Soft warning when a v0.2 file uses the legacy entry_points.description key
|
|
3
|
+
// instead of the renamed `purpose`. The v0.2 schema accepts either spelling
|
|
4
|
+
// (with a `not` clause forbidding both); this check nudges maintainers to
|
|
5
|
+
// migrate. v0.1 files using `description` are left alone.
|
|
6
|
+
export function entryPointAliasWarn(doc) {
|
|
7
|
+
const d = doc;
|
|
8
|
+
if (d?.anatomy_version !== "0.2")
|
|
9
|
+
return { errors: [], warnings: [] };
|
|
10
|
+
const eps = d?.operation?.entry_points;
|
|
11
|
+
if (!Array.isArray(eps))
|
|
12
|
+
return { errors: [], warnings: [] };
|
|
13
|
+
const warnings = [];
|
|
14
|
+
for (let i = 0; i < eps.length; i++) {
|
|
15
|
+
if (eps[i] && Object.prototype.hasOwnProperty.call(eps[i], "description")) {
|
|
16
|
+
warnings.push({
|
|
17
|
+
code: "entry-point-description-deprecated",
|
|
18
|
+
message: `entry_points[${i}].description is deprecated in v0.2; rename to 'purpose'`,
|
|
19
|
+
pointer: `/operation/entry_points/${i}/description`,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { errors: [], warnings };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=entry-point-alias-warn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-point-alias-warn.js","sourceRoot":"","sources":["../../src/checks/entry-point-alias-warn.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,6EAA6E;AAC7E,4EAA4E;AAC5E,0EAA0E;AAC1E,0DAA0D;AAI1D,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAI9C,MAAM,CAAC,GAAG,GAGT,CAAC;IAEF,IAAI,CAAC,EAAE,eAAe,KAAK,KAAK;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAEtE,MAAM,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC;IACvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAE7D,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,EAAE,CAAC;YAC1E,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,oCAAoC;gBAC1C,OAAO,EAAE,gBAAgB,CAAC,0DAA0D;gBACpF,OAAO,EAAE,2BAA2B,CAAC,cAAc;aACpD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint-check.d.ts","sourceRoot":"","sources":["../../src/checks/fingerprint-check.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG;IAC9C,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CA0DA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/checks/fingerprint-check.ts
|
|
2
|
+
// Verifies identity.fingerprint. Version-aware:
|
|
3
|
+
// v0.7+: flat identity strings → fingerprintFromPillars(stack, form, domain, function)
|
|
4
|
+
// v0.1–v0.6: nested pillar objects → concat of canonicalHash(id) per pillar
|
|
5
|
+
import { canonicalHash, fingerprintFromPillars } from "../canonical.js";
|
|
6
|
+
export function fingerprintCheck(doc) {
|
|
7
|
+
const errors = [];
|
|
8
|
+
const d = doc;
|
|
9
|
+
const identity = d.identity;
|
|
10
|
+
if (!identity || typeof identity.fingerprint !== "string")
|
|
11
|
+
return { errors, warnings: [] };
|
|
12
|
+
// Flat vs nested identity is a structural distinction, not a version one:
|
|
13
|
+
// v0.7+ uses plain string pillars; v0.1–v0.6 uses nested { id, hash }
|
|
14
|
+
// objects. Keying this on a hardcoded version set silently no-ops the
|
|
15
|
+
// check on any flat version not listed (it did so for v0.9–v0.15).
|
|
16
|
+
if (typeof identity.stack === "string") {
|
|
17
|
+
// Flat string pillars
|
|
18
|
+
const { stack, form, domain } = identity;
|
|
19
|
+
const fn = identity.function;
|
|
20
|
+
if (typeof stack !== "string" || typeof form !== "string" ||
|
|
21
|
+
typeof domain !== "string" || typeof fn !== "string")
|
|
22
|
+
return { errors, warnings: [] };
|
|
23
|
+
const expected = fingerprintFromPillars(stack, form, domain, fn);
|
|
24
|
+
if (identity.fingerprint !== expected) {
|
|
25
|
+
errors.push({
|
|
26
|
+
code: "fingerprint-mismatch",
|
|
27
|
+
message: "identity.fingerprint does not match fingerprintFromPillars(stack, form, domain, function)",
|
|
28
|
+
pointer: "/identity/fingerprint",
|
|
29
|
+
expected,
|
|
30
|
+
actual: identity.fingerprint,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Legacy nested pillar objects (v0.1–v0.6)
|
|
36
|
+
const legacyIdentity = identity;
|
|
37
|
+
const ids = [
|
|
38
|
+
legacyIdentity.stack?.id,
|
|
39
|
+
legacyIdentity.form?.id,
|
|
40
|
+
legacyIdentity.domain?.id,
|
|
41
|
+
legacyIdentity.function?.id,
|
|
42
|
+
];
|
|
43
|
+
const hashes = ids.map(canonicalHash);
|
|
44
|
+
if (hashes.some(h => h === null))
|
|
45
|
+
return { errors, warnings: [] };
|
|
46
|
+
const expected = hashes.join("");
|
|
47
|
+
if (identity.fingerprint !== expected) {
|
|
48
|
+
errors.push({
|
|
49
|
+
code: "fingerprint-mismatch",
|
|
50
|
+
message: "identity.fingerprint does not equal concat of pillar hashes",
|
|
51
|
+
pointer: "/identity/fingerprint",
|
|
52
|
+
expected,
|
|
53
|
+
actual: identity.fingerprint,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { errors, warnings: [] };
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=fingerprint-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint-check.js","sourceRoot":"","sources":["../../src/checks/fingerprint-check.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,gDAAgD;AAChD,yFAAyF;AACzF,8EAA8E;AAE9E,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGxE,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAI3C,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,MAAM,QAAQ,GAAG,CAAC,CAAC,QAA+C,CAAC;IACnE,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,CAAC,WAAW,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAE3F,0EAA0E;IAC1E,sEAAsE;IACtE,sEAAsE;IACtE,mEAAmE;IACnE,IAAI,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,sBAAsB;QACtB,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;QACzC,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC;QAC7B,IACE,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,QAAQ;YACrD,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ;YACpD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,sBAAsB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;QACjE,IAAI,QAAQ,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;YACtC,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,sBAAsB;gBAC5B,OAAO,EAAE,2FAA2F;gBACpG,OAAO,EAAE,uBAAuB;gBAChC,QAAQ;gBACR,MAAM,EAAE,QAAQ,CAAC,WAAW;aAC7B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;SAAM,CAAC;QACN,2CAA2C;QAC3C,MAAM,cAAc,GAAG,QAMtB,CAAC;QACF,MAAM,GAAG,GAAG;YACV,cAAc,CAAC,KAAK,EAAE,EAAE;YACxB,cAAc,CAAC,IAAI,EAAE,EAAE;YACvB,cAAc,CAAC,MAAM,EAAE,EAAE;YACzB,cAAc,CAAC,QAAQ,EAAE,EAAE;SAC5B,CAAC;QACF,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACtC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;YAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAClE,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,QAAQ,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;YACtC,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,sBAAsB;gBAC5B,OAAO,EAAE,6DAA6D;gBACtE,OAAO,EAAE,uBAAuB;gBAChC,QAAQ;gBACR,MAAM,EAAE,QAAQ,CAAC,WAAW;aAC7B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash-check.d.ts","sourceRoot":"","sources":["../../src/checks/hash-check.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAI7D,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG;IACvC,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CA2BA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/checks/hash-check.ts
|
|
2
|
+
// Verifies each identity.<pillar>.hash equals canonicalHash(id).
|
|
3
|
+
// No-op for v0.7+ documents (flat identity has no per-pillar hash fields).
|
|
4
|
+
import { canonicalHash } from "../canonical.js";
|
|
5
|
+
const PILLARS = ["stack", "form", "domain", "function"];
|
|
6
|
+
export function hashCheck(doc) {
|
|
7
|
+
const d = doc;
|
|
8
|
+
const errors = [];
|
|
9
|
+
const identity = d.identity;
|
|
10
|
+
if (!identity)
|
|
11
|
+
return { errors, warnings: [] };
|
|
12
|
+
// v0.7+ flat identity uses plain string pillars with no per-pillar hash
|
|
13
|
+
// fields — skip structurally rather than via a hardcoded version set
|
|
14
|
+
// (which silently fails to skip flat versions not listed).
|
|
15
|
+
if (typeof identity.stack === "string")
|
|
16
|
+
return { errors, warnings: [] };
|
|
17
|
+
for (const pillar of PILLARS) {
|
|
18
|
+
const p = identity[pillar];
|
|
19
|
+
if (!p || typeof p.id !== "string" || typeof p.hash !== "string")
|
|
20
|
+
continue;
|
|
21
|
+
const expected = canonicalHash(p.id);
|
|
22
|
+
if (expected === null)
|
|
23
|
+
continue;
|
|
24
|
+
if (p.hash !== expected) {
|
|
25
|
+
errors.push({
|
|
26
|
+
code: "hash-content-mismatch",
|
|
27
|
+
message: `identity.${pillar}.hash does not equal canonicalHash(id)`,
|
|
28
|
+
pointer: `/identity/${pillar}/hash`,
|
|
29
|
+
expected,
|
|
30
|
+
actual: p.hash,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { errors, warnings: [] };
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=hash-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash-check.js","sourceRoot":"","sources":["../../src/checks/hash-check.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,iEAAiE;AACjE,2EAA2E;AAE3E,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAU,CAAC;AAEjE,MAAM,UAAU,SAAS,CAAC,GAAY;IAIpC,MAAM,CAAC,GAAG,GAA+F,CAAC;IAC1G,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;IAC5B,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC/C,wEAAwE;IACxE,qEAAqE;IACrE,2DAA2D;IAC3D,IAAI,OAAQ,QAAoC,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAErG,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3B,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,SAAS;QAC3E,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,QAAQ,KAAK,IAAI;YAAE,SAAS;QAChC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACxB,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,uBAAuB;gBAC7B,OAAO,EAAE,YAAY,MAAM,wCAAwC;gBACnE,OAAO,EAAE,aAAa,MAAM,OAAO;gBACnC,QAAQ;gBACR,MAAM,EAAE,CAAC,CAAC,IAAI;aACf,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface-form-check.d.ts","sourceRoot":"","sources":["../../src/checks/interface-form-check.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAW7D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG;IAChD,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CAiDA"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/checks/interface-form-check.ts
|
|
2
|
+
// Enforces the form ↔ interface variant matrix from spec §7.2.
|
|
3
|
+
// Matching is by substring on the canonical form id, evaluated top-to-bottom;
|
|
4
|
+
// first match wins:
|
|
5
|
+
// contains "cli" → subcommands
|
|
6
|
+
// contains "api" → endpoints
|
|
7
|
+
// contains "service" → endpoints
|
|
8
|
+
// contains "library" → exports
|
|
9
|
+
// anything else → [interface] must be absent
|
|
10
|
+
// A form id like "cli-library" matches "cli" first; the only valid variant
|
|
11
|
+
// is "subcommands".
|
|
12
|
+
//
|
|
13
|
+
// Version-aware identity read:
|
|
14
|
+
// v0.7+: identity.form is a plain string (flat identity)
|
|
15
|
+
// v0.1-v0.6: identity.form is { id, hash } (nested-pillar identity)
|
|
16
|
+
const RULES = [
|
|
17
|
+
{ substr: "cli", variant: "subcommands" },
|
|
18
|
+
{ substr: "api", variant: "endpoints" },
|
|
19
|
+
{ substr: "service", variant: "endpoints" },
|
|
20
|
+
{ substr: "library", variant: "exports" },
|
|
21
|
+
];
|
|
22
|
+
const VARIANT_KEYS = ["exports", "endpoints", "subcommands"];
|
|
23
|
+
export function interfaceFormCheck(doc) {
|
|
24
|
+
const d = doc;
|
|
25
|
+
const iface = d?.interface;
|
|
26
|
+
if (!iface)
|
|
27
|
+
return { errors: [], warnings: [] };
|
|
28
|
+
// v0.7 has flat string pillars; earlier versions wrap each pillar in an
|
|
29
|
+
// object with an id. Read both shapes.
|
|
30
|
+
const formField = d?.identity?.form;
|
|
31
|
+
const formId = typeof formField === "string"
|
|
32
|
+
? formField
|
|
33
|
+
: (formField && typeof formField.id === "string" ? formField.id : undefined);
|
|
34
|
+
if (typeof formId !== "string")
|
|
35
|
+
return { errors: [], warnings: [] }; // schema-check catches missing form
|
|
36
|
+
const presentVariant = VARIANT_KEYS.find(k => iface[k] !== undefined);
|
|
37
|
+
if (!presentVariant)
|
|
38
|
+
return { errors: [], warnings: [] }; // schema's oneOf catches this
|
|
39
|
+
const matched = RULES.find(r => formId.includes(r.substr));
|
|
40
|
+
const allowed = matched?.variant;
|
|
41
|
+
if (!allowed) {
|
|
42
|
+
return {
|
|
43
|
+
errors: [{
|
|
44
|
+
code: "interface-form-mismatch",
|
|
45
|
+
message: `form id ${JSON.stringify(formId)} does not match any [interface] variant; [interface] must be absent`,
|
|
46
|
+
pointer: "/interface",
|
|
47
|
+
actual: formId,
|
|
48
|
+
}],
|
|
49
|
+
warnings: [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (presentVariant !== allowed) {
|
|
53
|
+
return {
|
|
54
|
+
errors: [{
|
|
55
|
+
code: "interface-form-mismatch",
|
|
56
|
+
message: `form id ${JSON.stringify(formId)} matches '${matched.substr}' (allowed variant: ${allowed}); got: ${presentVariant}`,
|
|
57
|
+
pointer: `/interface/${presentVariant}`,
|
|
58
|
+
expected: allowed,
|
|
59
|
+
actual: presentVariant,
|
|
60
|
+
}],
|
|
61
|
+
warnings: [],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { errors: [], warnings: [] };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=interface-form-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface-form-check.js","sourceRoot":"","sources":["../../src/checks/interface-form-check.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-dangling-ref-check.d.ts","sourceRoot":"","sources":["../../src/checks/memory-dangling-ref-check.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAM7D,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,GACf;IAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,CAyBpD"}
|