mrplex 0.0.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/LICENSE +21 -0
- package/README.md +284 -0
- package/dist/cli/config.d.ts +28 -0
- package/dist/cli/config.js +48 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +13 -0
- package/dist/cli/exit-codes.js +39 -0
- package/dist/cli/exit-codes.js.map +1 -0
- package/dist/cli/format.d.ts +22 -0
- package/dist/cli/format.js +148 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/main.d.ts +16 -0
- package/dist/cli/main.js +1518 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/client/kernel-client.d.ts +97 -0
- package/dist/client/kernel-client.js +14 -0
- package/dist/client/kernel-client.js.map +1 -0
- package/dist/client/local.d.ts +26 -0
- package/dist/client/local.js +103 -0
- package/dist/client/local.js.map +1 -0
- package/dist/client/remote-mcp.d.ts +35 -0
- package/dist/client/remote-mcp.js +174 -0
- package/dist/client/remote-mcp.js.map +1 -0
- package/dist/embed/backfill.d.ts +24 -0
- package/dist/embed/backfill.js +33 -0
- package/dist/embed/backfill.js.map +1 -0
- package/dist/embed/chunker.d.ts +31 -0
- package/dist/embed/chunker.js +121 -0
- package/dist/embed/chunker.js.map +1 -0
- package/dist/embed/cmd-hook.d.ts +21 -0
- package/dist/embed/cmd-hook.js +100 -0
- package/dist/embed/cmd-hook.js.map +1 -0
- package/dist/embed/config.d.ts +61 -0
- package/dist/embed/config.js +109 -0
- package/dist/embed/config.js.map +1 -0
- package/dist/embed/hook.d.ts +38 -0
- package/dist/embed/hook.js +56 -0
- package/dist/embed/hook.js.map +1 -0
- package/dist/embed/http-hook.d.ts +13 -0
- package/dist/embed/http-hook.js +39 -0
- package/dist/embed/http-hook.js.map +1 -0
- package/dist/embed/worker.d.ts +62 -0
- package/dist/embed/worker.js +228 -0
- package/dist/embed/worker.js.map +1 -0
- package/dist/fixtures/hwe/characters/albion-ever.md +12 -0
- package/dist/fixtures/hwe/characters/cent.md +11 -0
- package/dist/fixtures/hwe/concepts/cent-demiurge-resonance.md +10 -0
- package/dist/fixtures/hwe/concepts/phase-looper.md +10 -0
- package/dist/fixtures/hwe/entities/demiurge.md +11 -0
- package/dist/fixtures/hwe/entities/monad.md +11 -0
- package/dist/fixtures/hwe/moc/globocorp-network.md +19 -0
- package/dist/fixtures/hwe/organizations/globocorp.md +11 -0
- package/dist/fixtures/hwe/readme.md +24 -0
- package/dist/fixtures/starship/crew/aria-okonkwo.md +14 -0
- package/dist/fixtures/starship/crew/bexley-orr.md +14 -0
- package/dist/fixtures/starship/crew/dax-thorne.md +14 -0
- package/dist/fixtures/starship/crew/isolde-marsh.md +16 -0
- package/dist/fixtures/starship/crew/kestrel-vance.md +15 -0
- package/dist/fixtures/starship/crew/quill-vasquez.md +14 -0
- package/dist/fixtures/starship/crew/soren-halloway.md +16 -0
- package/dist/fixtures/starship/encounters/the-drift-choir.md +14 -0
- package/dist/fixtures/starship/encounters/the-hollow-signal.md +16 -0
- package/dist/fixtures/starship/encounters/the-wandering-buoy.md +14 -0
- package/dist/fixtures/starship/equipment/coolant-loop-b.md +13 -0
- package/dist/fixtures/starship/equipment/emergency-ration-bars.md +12 -0
- package/dist/fixtures/starship/equipment/long-range-sensors.md +13 -0
- package/dist/fixtures/starship/equipment/plasma-manifold-3.md +15 -0
- package/dist/fixtures/starship/equipment/shuttle-corvid.md +13 -0
- package/dist/fixtures/starship/logs/marsh-4419-1.md +13 -0
- package/dist/fixtures/starship/logs/okonkwo-4415-1.md +13 -0
- package/dist/fixtures/starship/logs/orr-4413-1.md +13 -0
- package/dist/fixtures/starship/logs/orr-4420-1.md +12 -0
- package/dist/fixtures/starship/logs/thorne-4413-2.md +13 -0
- package/dist/fixtures/starship/logs/vance-4419-2.md +13 -0
- package/dist/fixtures/starship/misc/the-galley-ficus.md +11 -0
- package/dist/fixtures/starship/missions/the-cinder-run.md +17 -0
- package/dist/fixtures/starship/missions/the-drift-choir.md +17 -0
- package/dist/fixtures/starship/missions/the-hollow-signal.md +17 -0
- package/dist/fixtures/starship/missions/the-silent-beacon.md +17 -0
- package/dist/fixtures/starship/moc/crew.md +19 -0
- package/dist/fixtures/starship/moc/encounters.md +14 -0
- package/dist/fixtures/starship/moc/missions.md +15 -0
- package/dist/fixtures/starship/readme.md +60 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/auth/glob.d.ts +46 -0
- package/dist/kernel/auth/glob.js +112 -0
- package/dist/kernel/auth/glob.js.map +1 -0
- package/dist/kernel/auth/scope.d.ts +53 -0
- package/dist/kernel/auth/scope.js +83 -0
- package/dist/kernel/auth/scope.js.map +1 -0
- package/dist/kernel/casefold.d.ts +37 -0
- package/dist/kernel/casefold.js +44 -0
- package/dist/kernel/casefold.js.map +1 -0
- package/dist/kernel/constants.d.ts +21 -0
- package/dist/kernel/constants.js +22 -0
- package/dist/kernel/constants.js.map +1 -0
- package/dist/kernel/context.d.ts +70 -0
- package/dist/kernel/context.js +114 -0
- package/dist/kernel/context.js.map +1 -0
- package/dist/kernel/deletion.d.ts +50 -0
- package/dist/kernel/deletion.js +83 -0
- package/dist/kernel/deletion.js.map +1 -0
- package/dist/kernel/diff.d.ts +36 -0
- package/dist/kernel/diff.js +61 -0
- package/dist/kernel/diff.js.map +1 -0
- package/dist/kernel/errors.d.ts +37 -0
- package/dist/kernel/errors.js +55 -0
- package/dist/kernel/errors.js.map +1 -0
- package/dist/kernel/frontmatter-input.d.ts +32 -0
- package/dist/kernel/frontmatter-input.js +85 -0
- package/dist/kernel/frontmatter-input.js.map +1 -0
- package/dist/kernel/graph.d.ts +34 -0
- package/dist/kernel/graph.js +559 -0
- package/dist/kernel/graph.js.map +1 -0
- package/dist/kernel/history.d.ts +74 -0
- package/dist/kernel/history.js +143 -0
- package/dist/kernel/history.js.map +1 -0
- package/dist/kernel/kernel.d.ts +162 -0
- package/dist/kernel/kernel.js +675 -0
- package/dist/kernel/kernel.js.map +1 -0
- package/dist/kernel/path-config.d.ts +101 -0
- package/dist/kernel/path-config.js +171 -0
- package/dist/kernel/path-config.js.map +1 -0
- package/dist/kernel/query/ast.d.ts +24 -0
- package/dist/kernel/query/ast.js +52 -0
- package/dist/kernel/query/ast.js.map +1 -0
- package/dist/kernel/query/cel-parse.d.ts +37 -0
- package/dist/kernel/query/cel-parse.js +88 -0
- package/dist/kernel/query/cel-parse.js.map +1 -0
- package/dist/kernel/query/degrees.d.ts +31 -0
- package/dist/kernel/query/degrees.js +135 -0
- package/dist/kernel/query/degrees.js.map +1 -0
- package/dist/kernel/query/graph-ast.d.ts +51 -0
- package/dist/kernel/query/graph-ast.js +139 -0
- package/dist/kernel/query/graph-ast.js.map +1 -0
- package/dist/kernel/query/query.d.ts +56 -0
- package/dist/kernel/query/query.js +276 -0
- package/dist/kernel/query/query.js.map +1 -0
- package/dist/kernel/validation.d.ts +64 -0
- package/dist/kernel/validation.js +183 -0
- package/dist/kernel/validation.js.map +1 -0
- package/dist/kernel/version-id.d.ts +10 -0
- package/dist/kernel/version-id.js +21 -0
- package/dist/kernel/version-id.js.map +1 -0
- package/dist/kernel/wire.d.ts +148 -0
- package/dist/kernel/wire.js +6 -0
- package/dist/kernel/wire.js.map +1 -0
- package/dist/links/backfill.d.ts +28 -0
- package/dist/links/backfill.js +33 -0
- package/dist/links/backfill.js.map +1 -0
- package/dist/links/extract.d.ts +29 -0
- package/dist/links/extract.js +233 -0
- package/dist/links/extract.js.map +1 -0
- package/dist/links/link-config.d.ts +70 -0
- package/dist/links/link-config.js +107 -0
- package/dist/links/link-config.js.map +1 -0
- package/dist/links/maintain.d.ts +37 -0
- package/dist/links/maintain.js +91 -0
- package/dist/links/maintain.js.map +1 -0
- package/dist/links/repair.d.ts +46 -0
- package/dist/links/repair.js +96 -0
- package/dist/links/repair.js.map +1 -0
- package/dist/links/resolve.d.ts +41 -0
- package/dist/links/resolve.js +121 -0
- package/dist/links/resolve.js.map +1 -0
- package/dist/links/stale.d.ts +32 -0
- package/dist/links/stale.js +73 -0
- package/dist/links/stale.js.map +1 -0
- package/dist/markdown/content-hash.d.ts +34 -0
- package/dist/markdown/content-hash.js +46 -0
- package/dist/markdown/content-hash.js.map +1 -0
- package/dist/markdown/frontmatter.d.ts +69 -0
- package/dist/markdown/frontmatter.js +147 -0
- package/dist/markdown/frontmatter.js.map +1 -0
- package/dist/markdown/hash-backfill.d.ts +22 -0
- package/dist/markdown/hash-backfill.js +43 -0
- package/dist/markdown/hash-backfill.js.map +1 -0
- package/dist/mcp/query-syntax.d.ts +20 -0
- package/dist/mcp/query-syntax.js +194 -0
- package/dist/mcp/query-syntax.js.map +1 -0
- package/dist/mcp/render.d.ts +31 -0
- package/dist/mcp/render.js +110 -0
- package/dist/mcp/render.js.map +1 -0
- package/dist/mcp/server.d.ts +60 -0
- package/dist/mcp/server.js +170 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +93 -0
- package/dist/mcp/tools.js +1218 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/rest/conditional.d.ts +37 -0
- package/dist/rest/conditional.js +58 -0
- package/dist/rest/conditional.js.map +1 -0
- package/dist/rest/negotiate.d.ts +33 -0
- package/dist/rest/negotiate.js +54 -0
- package/dist/rest/negotiate.js.map +1 -0
- package/dist/rest/routes.d.ts +37 -0
- package/dist/rest/routes.js +744 -0
- package/dist/rest/routes.js.map +1 -0
- package/dist/seed/fixture-seed.d.ts +35 -0
- package/dist/seed/fixture-seed.js +75 -0
- package/dist/seed/fixture-seed.js.map +1 -0
- package/dist/seed/starship-config.d.ts +3 -0
- package/dist/seed/starship-config.js +5 -0
- package/dist/seed/starship-config.js.map +1 -0
- package/dist/server/headers.d.ts +36 -0
- package/dist/server/headers.js +30 -0
- package/dist/server/headers.js.map +1 -0
- package/dist/server/http-error.d.ts +53 -0
- package/dist/server/http-error.js +104 -0
- package/dist/server/http-error.js.map +1 -0
- package/dist/server/serve.d.ts +56 -0
- package/dist/server/serve.js +135 -0
- package/dist/server/serve.js.map +1 -0
- package/dist/shell/audit.d.ts +32 -0
- package/dist/shell/audit.js +28 -0
- package/dist/shell/audit.js.map +1 -0
- package/dist/shell/guard.d.ts +50 -0
- package/dist/shell/guard.js +176 -0
- package/dist/shell/guard.js.map +1 -0
- package/dist/shell/keys.d.ts +36 -0
- package/dist/shell/keys.js +64 -0
- package/dist/shell/keys.js.map +1 -0
- package/dist/shell/login.d.ts +56 -0
- package/dist/shell/login.js +130 -0
- package/dist/shell/login.js.map +1 -0
- package/dist/shell/oidc.d.ts +62 -0
- package/dist/shell/oidc.js +102 -0
- package/dist/shell/oidc.js.map +1 -0
- package/dist/shell/policy.d.ts +100 -0
- package/dist/shell/policy.js +232 -0
- package/dist/shell/policy.js.map +1 -0
- package/dist/shell/proxy-policy.d.ts +34 -0
- package/dist/shell/proxy-policy.js +120 -0
- package/dist/shell/proxy-policy.js.map +1 -0
- package/dist/shell/proxy.d.ts +47 -0
- package/dist/shell/proxy.js +253 -0
- package/dist/shell/proxy.js.map +1 -0
- package/dist/shell/serve.d.ts +86 -0
- package/dist/shell/serve.js +0 -0
- package/dist/shell/serve.js.map +1 -0
- package/dist/shell/stdio.d.ts +55 -0
- package/dist/shell/stdio.js +63 -0
- package/dist/shell/stdio.js.map +1 -0
- package/dist/storage/registry.d.ts +20 -0
- package/dist/storage/registry.js +38 -0
- package/dist/storage/registry.js.map +1 -0
- package/dist/storage/search-plan.d.ts +93 -0
- package/dist/storage/search-plan.js +15 -0
- package/dist/storage/search-plan.js.map +1 -0
- package/dist/storage/types.d.ts +448 -0
- package/dist/storage/types.js +14 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/storage/versions-since.d.ts +72 -0
- package/dist/storage/versions-since.js +92 -0
- package/dist/storage/versions-since.js.map +1 -0
- package/dist/storage-postgres/adapter.d.ts +17 -0
- package/dist/storage-postgres/adapter.js +782 -0
- package/dist/storage-postgres/adapter.js.map +1 -0
- package/dist/storage-postgres/compile-postgres.d.ts +22 -0
- package/dist/storage-postgres/compile-postgres.js +830 -0
- package/dist/storage-postgres/compile-postgres.js.map +1 -0
- package/dist/storage-postgres/errors.d.ts +34 -0
- package/dist/storage-postgres/errors.js +50 -0
- package/dist/storage-postgres/errors.js.map +1 -0
- package/dist/storage-postgres/migrations/0001_init.sql +113 -0
- package/dist/storage-postgres/migrations/0002_content_hash.sql +7 -0
- package/dist/storage-postgres/migrations/index.d.ts +22 -0
- package/dist/storage-postgres/migrations/index.js +70 -0
- package/dist/storage-postgres/migrations/index.js.map +1 -0
- package/dist/storage-sqlite/adapter.d.ts +2 -0
- package/dist/storage-sqlite/adapter.js +688 -0
- package/dist/storage-sqlite/adapter.js.map +1 -0
- package/dist/storage-sqlite/compile-filter.d.ts +41 -0
- package/dist/storage-sqlite/compile-filter.js +881 -0
- package/dist/storage-sqlite/compile-filter.js.map +1 -0
- package/dist/storage-sqlite/compile-sqlite.d.ts +19 -0
- package/dist/storage-sqlite/compile-sqlite.js +176 -0
- package/dist/storage-sqlite/compile-sqlite.js.map +1 -0
- package/dist/storage-sqlite/migrations/0001_init.sql +123 -0
- package/dist/storage-sqlite/migrations/0002_content_hash.sql +7 -0
- package/dist/storage-sqlite/migrations/index.d.ts +6 -0
- package/dist/storage-sqlite/migrations/index.js +41 -0
- package/dist/storage-sqlite/migrations/index.js.map +1 -0
- package/dist/storage-sqlite/vec.d.ts +40 -0
- package/dist/storage-sqlite/vec.js +62 -0
- package/dist/storage-sqlite/vec.js.map +1 -0
- package/dist/sync/cursor.d.ts +36 -0
- package/dist/sync/cursor.js +66 -0
- package/dist/sync/cursor.js.map +1 -0
- package/dist/sync/daemon.d.ts +40 -0
- package/dist/sync/daemon.js +202 -0
- package/dist/sync/daemon.js.map +1 -0
- package/dist/sync/feed.d.ts +38 -0
- package/dist/sync/feed.js +125 -0
- package/dist/sync/feed.js.map +1 -0
- package/dist/sync/fs-store.d.ts +8 -0
- package/dist/sync/fs-store.js +76 -0
- package/dist/sync/fs-store.js.map +1 -0
- package/dist/sync/intrinsics.d.ts +52 -0
- package/dist/sync/intrinsics.js +76 -0
- package/dist/sync/intrinsics.js.map +1 -0
- package/dist/sync/paths.d.ts +38 -0
- package/dist/sync/paths.js +56 -0
- package/dist/sync/paths.js.map +1 -0
- package/dist/sync/push.d.ts +48 -0
- package/dist/sync/push.js +328 -0
- package/dist/sync/push.js.map +1 -0
- package/dist/sync/reconcile.d.ts +54 -0
- package/dist/sync/reconcile.js +252 -0
- package/dist/sync/reconcile.js.map +1 -0
- package/dist/sync/run.d.ts +27 -0
- package/dist/sync/run.js +46 -0
- package/dist/sync/run.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Case & Unicode folding for path/slug IDENTITY — design §3.5.1 amendment
|
|
3
|
+
* (case-folding-plan.md WS1).
|
|
4
|
+
*
|
|
5
|
+
* `normalizeKey` computes the derived key that drives case-insensitive,
|
|
6
|
+
* Unicode-normalized uniqueness and by-key lookups. Storage stays
|
|
7
|
+
* case- and form-preserving (we store the author's exact bytes); this
|
|
8
|
+
* key is an index artifact, never surfaced on the wire (§3.3-style
|
|
9
|
+
* internal-only, decision 7).
|
|
10
|
+
*
|
|
11
|
+
* THE ONE AUTHORITY. Both storage adapters receive the pre-computed key
|
|
12
|
+
* from the kernel — neither calls SQL `lower()`, `COLLATE NOCASE`, or
|
|
13
|
+
* `citext`. That is deliberate: SQLite's `lower()` is ASCII-only without
|
|
14
|
+
* ICU, while Postgres's is locale-aware, so a functional index would
|
|
15
|
+
* silently diverge on non-ASCII input and break the §7.2 adapter-parity
|
|
16
|
+
* guarantee. Folding in JS, here, keeps the two engines identical.
|
|
17
|
+
*
|
|
18
|
+
* Strength (decision 4): **NFC + locale-invariant lowercase.** This folds
|
|
19
|
+
* all accented Latin/Greek/Cyrillic and the common case, and makes NFC/NFD
|
|
20
|
+
* spellings of the same text compare equal. It deliberately does NOT fold
|
|
21
|
+
* the German ß→ss, ligatures (fi→fi), or Greek final-sigma — those need the
|
|
22
|
+
* full Unicode default case-fold table, an additive upgrade later (the key
|
|
23
|
+
* is recomputed by a backfill, so strengthening it is non-breaking beyond a
|
|
24
|
+
* re-index). casefold.test.ts pins the exact boundary.
|
|
25
|
+
*
|
|
26
|
+
* `toLowerCase()` (NOT `toLocaleLowerCase()`) is used on purpose: a locale
|
|
27
|
+
* fold would map Turkish `I`→`ı`, making document identity depend on the
|
|
28
|
+
* server's locale. Identity must be locale-independent.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Fold a path or slug to its identity key. Idempotent, total, pure.
|
|
32
|
+
*
|
|
33
|
+
* normalizeKey("Alice.md") === "alice.md"
|
|
34
|
+
* normalizeKey("café.md" as NFD) === normalizeKey("café.md" as NFC)
|
|
35
|
+
* normalizeKey(normalizeKey(x)) === normalizeKey(x)
|
|
36
|
+
*/
|
|
37
|
+
export declare function normalizeKey(s: string): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Case & Unicode folding for path/slug IDENTITY — design §3.5.1 amendment
|
|
3
|
+
* (case-folding-plan.md WS1).
|
|
4
|
+
*
|
|
5
|
+
* `normalizeKey` computes the derived key that drives case-insensitive,
|
|
6
|
+
* Unicode-normalized uniqueness and by-key lookups. Storage stays
|
|
7
|
+
* case- and form-preserving (we store the author's exact bytes); this
|
|
8
|
+
* key is an index artifact, never surfaced on the wire (§3.3-style
|
|
9
|
+
* internal-only, decision 7).
|
|
10
|
+
*
|
|
11
|
+
* THE ONE AUTHORITY. Both storage adapters receive the pre-computed key
|
|
12
|
+
* from the kernel — neither calls SQL `lower()`, `COLLATE NOCASE`, or
|
|
13
|
+
* `citext`. That is deliberate: SQLite's `lower()` is ASCII-only without
|
|
14
|
+
* ICU, while Postgres's is locale-aware, so a functional index would
|
|
15
|
+
* silently diverge on non-ASCII input and break the §7.2 adapter-parity
|
|
16
|
+
* guarantee. Folding in JS, here, keeps the two engines identical.
|
|
17
|
+
*
|
|
18
|
+
* Strength (decision 4): **NFC + locale-invariant lowercase.** This folds
|
|
19
|
+
* all accented Latin/Greek/Cyrillic and the common case, and makes NFC/NFD
|
|
20
|
+
* spellings of the same text compare equal. It deliberately does NOT fold
|
|
21
|
+
* the German ß→ss, ligatures (fi→fi), or Greek final-sigma — those need the
|
|
22
|
+
* full Unicode default case-fold table, an additive upgrade later (the key
|
|
23
|
+
* is recomputed by a backfill, so strengthening it is non-breaking beyond a
|
|
24
|
+
* re-index). casefold.test.ts pins the exact boundary.
|
|
25
|
+
*
|
|
26
|
+
* `toLowerCase()` (NOT `toLocaleLowerCase()`) is used on purpose: a locale
|
|
27
|
+
* fold would map Turkish `I`→`ı`, making document identity depend on the
|
|
28
|
+
* server's locale. Identity must be locale-independent.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Fold a path or slug to its identity key. Idempotent, total, pure.
|
|
32
|
+
*
|
|
33
|
+
* normalizeKey("Alice.md") === "alice.md"
|
|
34
|
+
* normalizeKey("café.md" as NFD) === normalizeKey("café.md" as NFC)
|
|
35
|
+
* normalizeKey(normalizeKey(x)) === normalizeKey(x)
|
|
36
|
+
*/
|
|
37
|
+
export function normalizeKey(s) {
|
|
38
|
+
// Fold case, then re-normalize: `toLowerCase()` can, for a few code
|
|
39
|
+
// points, emit a sequence that isn't NFC, which would break the
|
|
40
|
+
// idempotency contract (normalizeKey∘normalizeKey === normalizeKey).
|
|
41
|
+
// The trailing NFC pass makes the output a stable fixed point.
|
|
42
|
+
return s.normalize("NFC").toLowerCase().normalize("NFC");
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=casefold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"casefold.js","sourceRoot":"","sources":["../../src/kernel/casefold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,oEAAoE;IACpE,gEAAgE;IAChE,qEAAqE;IACrE,+DAA+D;IAC/D,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-cutting kernel constants. Kept small on purpose — this is not a
|
|
3
|
+
* grab-bag. Only things with more than one caller and no better home.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Intrinsic sigil — the leading character that marks an identifier as
|
|
7
|
+
* server-owned rather than user-authored. Single source of truth.
|
|
8
|
+
*
|
|
9
|
+
* Two surfaces share it:
|
|
10
|
+
*
|
|
11
|
+
* • Query filter syntax — `$path`, `$updated_at`, `$body`, and other
|
|
12
|
+
* intrinsics recognized by the CEL parser (see ../kernel/query/cel-parse.ts).
|
|
13
|
+
* • Frontmatter system properties — `$version` and (future) `$author`,
|
|
14
|
+
* `$updated_at`, etc., injected into GET responses and stripped on
|
|
15
|
+
* write (see ../markdown/frontmatter.ts).
|
|
16
|
+
*
|
|
17
|
+
* Not to be confused with `PathConfig.system_sigils` in wire.ts, which
|
|
18
|
+
* governs a different reservation entirely (path-segment prefixes like
|
|
19
|
+
* `.` / `:` gating hidden and deleted namespaces).
|
|
20
|
+
*/
|
|
21
|
+
export declare const INTRINSIC_SIGIL = "$";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-cutting kernel constants. Kept small on purpose — this is not a
|
|
3
|
+
* grab-bag. Only things with more than one caller and no better home.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Intrinsic sigil — the leading character that marks an identifier as
|
|
7
|
+
* server-owned rather than user-authored. Single source of truth.
|
|
8
|
+
*
|
|
9
|
+
* Two surfaces share it:
|
|
10
|
+
*
|
|
11
|
+
* • Query filter syntax — `$path`, `$updated_at`, `$body`, and other
|
|
12
|
+
* intrinsics recognized by the CEL parser (see ../kernel/query/cel-parse.ts).
|
|
13
|
+
* • Frontmatter system properties — `$version` and (future) `$author`,
|
|
14
|
+
* `$updated_at`, etc., injected into GET responses and stripped on
|
|
15
|
+
* write (see ../markdown/frontmatter.ts).
|
|
16
|
+
*
|
|
17
|
+
* Not to be confused with `PathConfig.system_sigils` in wire.ts, which
|
|
18
|
+
* governs a different reservation entirely (path-segment prefixes like
|
|
19
|
+
* `.` / `:` gating hidden and deleted namespaces).
|
|
20
|
+
*/
|
|
21
|
+
export const INTRINSIC_SIGIL = "$";
|
|
22
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/kernel/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CallContext — the uniform first parameter to every kernel op (design §8,
|
|
3
|
+
* noauth plan §1).
|
|
4
|
+
*
|
|
5
|
+
* mrplex is a full-trust kernel: any caller that can reach it can do anything.
|
|
6
|
+
* There is no resolved identity, no token, no admin bit. What a caller supplies
|
|
7
|
+
* per call is:
|
|
8
|
+
*
|
|
9
|
+
* • `author` — an opaque string stamped on the versions it writes. The engine
|
|
10
|
+
* attaches no semantics (the `Full Name <email>` convention is caller-side).
|
|
11
|
+
* Defaults to "mrplex". Sanity-validated only (non-empty, no control chars,
|
|
12
|
+
* length-capped) — never parsed.
|
|
13
|
+
* • `scope` — optional read-visibility narrowing. Absent = everything visible.
|
|
14
|
+
* Present = the call sees only what the claims grant (silent filtering on
|
|
15
|
+
* `query`; `forbidden` on a targeted read outside the claim). This is the
|
|
16
|
+
* one enforcement seam a fronting shell cannot replicate from outside, so
|
|
17
|
+
* it stays in the engine — exposed as a plain input, not derived from a
|
|
18
|
+
* token.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* A territory claim. `repo` is a slug, glob, or "*" — evaluated against the
|
|
22
|
+
* repos existing at call time, every call (no issuance snapshot, no id
|
|
23
|
+
* binding). `paths` is a gitignore-style path glob list (§8.2 semantics).
|
|
24
|
+
*
|
|
25
|
+
* Deliberately direction-neutral: a claim names repos/paths, not a direction.
|
|
26
|
+
* Meaning comes from where it sits — `ctx.scope` is read visibility; the
|
|
27
|
+
* auth-shell reuses the same type for its shell-enforced write matcher.
|
|
28
|
+
*/
|
|
29
|
+
export type ScopeClaim = {
|
|
30
|
+
repo: string | string[];
|
|
31
|
+
paths?: string | string[];
|
|
32
|
+
};
|
|
33
|
+
export type CallContext = {
|
|
34
|
+
/** Writes: opaque author string. Default "mrplex". */
|
|
35
|
+
author?: string;
|
|
36
|
+
/** Reads: visibility claims. Absent = everything visible. */
|
|
37
|
+
scope?: ScopeClaim[];
|
|
38
|
+
};
|
|
39
|
+
/** Identity stamped on a write when the caller supplies no author. */
|
|
40
|
+
export declare const DEFAULT_AUTHOR = "mrplex";
|
|
41
|
+
/**
|
|
42
|
+
* Structurally validate an already-parsed value as `ScopeClaim[]`. This is the
|
|
43
|
+
* single validation path shared by every surface that accepts a scope — the
|
|
44
|
+
* `X-Mrplex-Scope` header (via [[parseScopeClaims]]), the `POST /query` body
|
|
45
|
+
* `scope` field, the MCP query tool-arg, and the CLI `--scope` flag. Routing
|
|
46
|
+
* all of them here means a claim missing `repo` (or with wrong types) is a loud
|
|
47
|
+
* `filter_invalid`, not a silent `deny_all` empty result (see the note in
|
|
48
|
+
* kernel/auth/scope.ts on how `normalizeClaims` treats a repo-less claim).
|
|
49
|
+
*
|
|
50
|
+
* Throws `KernelError("filter_invalid")` on bad input so surfaces map it to a
|
|
51
|
+
* 4xx uniformly rather than a 500.
|
|
52
|
+
*/
|
|
53
|
+
export declare function validateScopeClaims(parsed: unknown): ScopeClaim[];
|
|
54
|
+
/**
|
|
55
|
+
* Parse a JSON string into `ScopeClaim[]`. Used to decode the `X-Mrplex-Scope`
|
|
56
|
+
* header and the CLI `--scope` flag. Throws `KernelError("filter_invalid")` on
|
|
57
|
+
* malformed input so a bad claim is a loud client error, not a silent
|
|
58
|
+
* full-access fallback.
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseScopeClaims(json: string): ScopeClaim[];
|
|
61
|
+
/**
|
|
62
|
+
* Resolve + validate the author for a write. Opaque: we sanity-check only
|
|
63
|
+
* (non-empty, no control characters, length-capped) and never parse the value.
|
|
64
|
+
* An empty/absent author yields the default "mrplex".
|
|
65
|
+
*
|
|
66
|
+
* Throws `KernelError("filter_invalid")` on bad input — the author is reachable
|
|
67
|
+
* verbatim from the `X-Mrplex-Author` header, so a control-char or over-length
|
|
68
|
+
* value is a client mistake and must stay in the 4xx band, not surface as a 500.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveAuthor(ctx: CallContext): string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CallContext — the uniform first parameter to every kernel op (design §8,
|
|
3
|
+
* noauth plan §1).
|
|
4
|
+
*
|
|
5
|
+
* mrplex is a full-trust kernel: any caller that can reach it can do anything.
|
|
6
|
+
* There is no resolved identity, no token, no admin bit. What a caller supplies
|
|
7
|
+
* per call is:
|
|
8
|
+
*
|
|
9
|
+
* • `author` — an opaque string stamped on the versions it writes. The engine
|
|
10
|
+
* attaches no semantics (the `Full Name <email>` convention is caller-side).
|
|
11
|
+
* Defaults to "mrplex". Sanity-validated only (non-empty, no control chars,
|
|
12
|
+
* length-capped) — never parsed.
|
|
13
|
+
* • `scope` — optional read-visibility narrowing. Absent = everything visible.
|
|
14
|
+
* Present = the call sees only what the claims grant (silent filtering on
|
|
15
|
+
* `query`; `forbidden` on a targeted read outside the claim). This is the
|
|
16
|
+
* one enforcement seam a fronting shell cannot replicate from outside, so
|
|
17
|
+
* it stays in the engine — exposed as a plain input, not derived from a
|
|
18
|
+
* token.
|
|
19
|
+
*/
|
|
20
|
+
import { KernelError } from "./errors.js";
|
|
21
|
+
/** Identity stamped on a write when the caller supplies no author. */
|
|
22
|
+
export const DEFAULT_AUTHOR = "mrplex";
|
|
23
|
+
const MAX_AUTHOR_LENGTH = 512;
|
|
24
|
+
/** True if `s` contains any C0/C1 control character (incl. tab/newline). */
|
|
25
|
+
function hasControlChar(s) {
|
|
26
|
+
for (let i = 0; i < s.length; i++) {
|
|
27
|
+
const c = s.charCodeAt(i);
|
|
28
|
+
if (c <= 0x1f || (c >= 0x7f && c <= 0x9f))
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Structurally validate an already-parsed value as `ScopeClaim[]`. This is the
|
|
35
|
+
* single validation path shared by every surface that accepts a scope — the
|
|
36
|
+
* `X-Mrplex-Scope` header (via [[parseScopeClaims]]), the `POST /query` body
|
|
37
|
+
* `scope` field, the MCP query tool-arg, and the CLI `--scope` flag. Routing
|
|
38
|
+
* all of them here means a claim missing `repo` (or with wrong types) is a loud
|
|
39
|
+
* `filter_invalid`, not a silent `deny_all` empty result (see the note in
|
|
40
|
+
* kernel/auth/scope.ts on how `normalizeClaims` treats a repo-less claim).
|
|
41
|
+
*
|
|
42
|
+
* Throws `KernelError("filter_invalid")` on bad input so surfaces map it to a
|
|
43
|
+
* 4xx uniformly rather than a 500.
|
|
44
|
+
*/
|
|
45
|
+
export function validateScopeClaims(parsed) {
|
|
46
|
+
if (!Array.isArray(parsed)) {
|
|
47
|
+
throw new KernelError("filter_invalid", { reason: "scope must be an array of claims" });
|
|
48
|
+
}
|
|
49
|
+
const isStrOrStrList = (v) => typeof v === "string" || (Array.isArray(v) && v.every((x) => typeof x === "string"));
|
|
50
|
+
for (const entry of parsed) {
|
|
51
|
+
if (typeof entry !== "object" || entry === null) {
|
|
52
|
+
throw new KernelError("filter_invalid", { reason: "scope claim must be an object" });
|
|
53
|
+
}
|
|
54
|
+
const e = entry;
|
|
55
|
+
if (!isStrOrStrList(e.repo)) {
|
|
56
|
+
throw new KernelError("filter_invalid", {
|
|
57
|
+
reason: "scope claim `repo` must be a string or string[]",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (e.paths !== undefined && !isStrOrStrList(e.paths)) {
|
|
61
|
+
throw new KernelError("filter_invalid", {
|
|
62
|
+
reason: "scope claim `paths` must be a string or string[]",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return parsed;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parse a JSON string into `ScopeClaim[]`. Used to decode the `X-Mrplex-Scope`
|
|
70
|
+
* header and the CLI `--scope` flag. Throws `KernelError("filter_invalid")` on
|
|
71
|
+
* malformed input so a bad claim is a loud client error, not a silent
|
|
72
|
+
* full-access fallback.
|
|
73
|
+
*/
|
|
74
|
+
export function parseScopeClaims(json) {
|
|
75
|
+
let parsed;
|
|
76
|
+
try {
|
|
77
|
+
parsed = JSON.parse(json);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
throw new KernelError("filter_invalid", {
|
|
81
|
+
reason: "scope must be valid JSON (a ScopeClaim array)",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return validateScopeClaims(parsed);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve + validate the author for a write. Opaque: we sanity-check only
|
|
88
|
+
* (non-empty, no control characters, length-capped) and never parse the value.
|
|
89
|
+
* An empty/absent author yields the default "mrplex".
|
|
90
|
+
*
|
|
91
|
+
* Throws `KernelError("filter_invalid")` on bad input — the author is reachable
|
|
92
|
+
* verbatim from the `X-Mrplex-Author` header, so a control-char or over-length
|
|
93
|
+
* value is a client mistake and must stay in the 4xx band, not surface as a 500.
|
|
94
|
+
*/
|
|
95
|
+
export function resolveAuthor(ctx) {
|
|
96
|
+
const raw = ctx.author;
|
|
97
|
+
if (raw === undefined || raw === "")
|
|
98
|
+
return DEFAULT_AUTHOR;
|
|
99
|
+
if (typeof raw !== "string") {
|
|
100
|
+
throw new KernelError("filter_invalid", { reason: "author must be a string" });
|
|
101
|
+
}
|
|
102
|
+
if (raw.length > MAX_AUTHOR_LENGTH) {
|
|
103
|
+
throw new KernelError("filter_invalid", {
|
|
104
|
+
reason: `author exceeds ${MAX_AUTHOR_LENGTH} characters`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (hasControlChar(raw)) {
|
|
108
|
+
throw new KernelError("filter_invalid", {
|
|
109
|
+
reason: "author must not contain control characters",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return raw;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/kernel/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deletion path builder — design §3.4.
|
|
3
|
+
*
|
|
4
|
+
* path/to/document.md @ v45129 → :deleted/path/to/document-v45129.md
|
|
5
|
+
*
|
|
6
|
+
* The version-id suffix goes before the file extension so type detection,
|
|
7
|
+
* syntax highlighting, and globs like `**\/*.md` keep working on trashed docs.
|
|
8
|
+
* The extension is "everything from the final segment's last '.'" — with the
|
|
9
|
+
* key caveat that a leading dot doesn't count. So:
|
|
10
|
+
*
|
|
11
|
+
* README → README-v45129
|
|
12
|
+
* .gitignore → .gitignore-v45129
|
|
13
|
+
* notes/foo.tar.gz → notes/foo.tar-v45129.gz
|
|
14
|
+
* notes/a-v42.md → notes/a-v42-v45129.md (uniqueness still holds:
|
|
15
|
+
* version_ids are unique
|
|
16
|
+
* and the suffix is
|
|
17
|
+
* always appended)
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Split the final segment of a path into `(basename, extension)` per the
|
|
21
|
+
* "final dot, but not a leading dot" rule from §3.4.
|
|
22
|
+
*
|
|
23
|
+
* Returned extension includes its leading dot (e.g. `.md`), or is `""` when
|
|
24
|
+
* there is no extension.
|
|
25
|
+
*/
|
|
26
|
+
export declare function splitExtension(finalSegment: string): {
|
|
27
|
+
basename: string;
|
|
28
|
+
extension: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Insert a version-id suffix before the extension of the final path segment.
|
|
32
|
+
* Does NOT prepend the deletion sigil — that's `deletionPath`'s job.
|
|
33
|
+
*/
|
|
34
|
+
export declare function withVersionSuffix(path: string, versionIdString: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* The kernel-emitted path for a deletion move. `<systemSigil>` is the
|
|
37
|
+
* canonical (first-entry) system sigil from the effective path config
|
|
38
|
+
* (design §3.5.4 — set for input, first for output).
|
|
39
|
+
*
|
|
40
|
+
* Example:
|
|
41
|
+
* deletionPath(":", "notes/foo.md", "v45129") = ":deleted/notes/foo-v45129.md"
|
|
42
|
+
*/
|
|
43
|
+
export declare function deletionPath(systemSigil: string, originalPath: string, versionIdString: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Rough classifier used by `docs.delete` idempotency (§4.1 rule 4): true if
|
|
46
|
+
* ANY segment of the path starts with one of the accepted system sigils.
|
|
47
|
+
* (The design-wide primitive lives in validation.pathHasSigilSegment; this
|
|
48
|
+
* shim keeps the naming intent-facing at the call site.)
|
|
49
|
+
*/
|
|
50
|
+
export declare function pathIsInSystemNamespace(path: string, systemSigils: readonly string[]): boolean;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deletion path builder — design §3.4.
|
|
3
|
+
*
|
|
4
|
+
* path/to/document.md @ v45129 → :deleted/path/to/document-v45129.md
|
|
5
|
+
*
|
|
6
|
+
* The version-id suffix goes before the file extension so type detection,
|
|
7
|
+
* syntax highlighting, and globs like `**\/*.md` keep working on trashed docs.
|
|
8
|
+
* The extension is "everything from the final segment's last '.'" — with the
|
|
9
|
+
* key caveat that a leading dot doesn't count. So:
|
|
10
|
+
*
|
|
11
|
+
* README → README-v45129
|
|
12
|
+
* .gitignore → .gitignore-v45129
|
|
13
|
+
* notes/foo.tar.gz → notes/foo.tar-v45129.gz
|
|
14
|
+
* notes/a-v42.md → notes/a-v42-v45129.md (uniqueness still holds:
|
|
15
|
+
* version_ids are unique
|
|
16
|
+
* and the suffix is
|
|
17
|
+
* always appended)
|
|
18
|
+
*/
|
|
19
|
+
import { PATH_SEPARATOR } from "./validation.js";
|
|
20
|
+
/**
|
|
21
|
+
* Split the final segment of a path into `(basename, extension)` per the
|
|
22
|
+
* "final dot, but not a leading dot" rule from §3.4.
|
|
23
|
+
*
|
|
24
|
+
* Returned extension includes its leading dot (e.g. `.md`), or is `""` when
|
|
25
|
+
* there is no extension.
|
|
26
|
+
*/
|
|
27
|
+
export function splitExtension(finalSegment) {
|
|
28
|
+
// A leading dot doesn't count as an extension separator — .gitignore is
|
|
29
|
+
// basename `.gitignore`, extension "".
|
|
30
|
+
const searchFrom = finalSegment.startsWith(".") ? 1 : 0;
|
|
31
|
+
const dot = finalSegment.lastIndexOf(".");
|
|
32
|
+
if (dot < searchFrom || dot === finalSegment.length - 1) {
|
|
33
|
+
// Either no dot at all (dot === -1 < searchFrom), or the only dot is the
|
|
34
|
+
// leading one, or the dot is trailing (bare "foo." would be basename
|
|
35
|
+
// "foo." — but §3.5 disallows most weird chars anyway).
|
|
36
|
+
if (dot === -1 || dot < searchFrom) {
|
|
37
|
+
return { basename: finalSegment, extension: "" };
|
|
38
|
+
}
|
|
39
|
+
return { basename: finalSegment.slice(0, dot), extension: finalSegment.slice(dot) };
|
|
40
|
+
}
|
|
41
|
+
return { basename: finalSegment.slice(0, dot), extension: finalSegment.slice(dot) };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Insert a version-id suffix before the extension of the final path segment.
|
|
45
|
+
* Does NOT prepend the deletion sigil — that's `deletionPath`'s job.
|
|
46
|
+
*/
|
|
47
|
+
export function withVersionSuffix(path, versionIdString) {
|
|
48
|
+
const segments = path.split(PATH_SEPARATOR);
|
|
49
|
+
const last = segments[segments.length - 1] ?? "";
|
|
50
|
+
const { basename, extension } = splitExtension(last);
|
|
51
|
+
segments[segments.length - 1] = `${basename}-${versionIdString}${extension}`;
|
|
52
|
+
return segments.join(PATH_SEPARATOR);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The kernel-emitted path for a deletion move. `<systemSigil>` is the
|
|
56
|
+
* canonical (first-entry) system sigil from the effective path config
|
|
57
|
+
* (design §3.5.4 — set for input, first for output).
|
|
58
|
+
*
|
|
59
|
+
* Example:
|
|
60
|
+
* deletionPath(":", "notes/foo.md", "v45129") = ":deleted/notes/foo-v45129.md"
|
|
61
|
+
*/
|
|
62
|
+
export function deletionPath(systemSigil, originalPath, versionIdString) {
|
|
63
|
+
return `${systemSigil}deleted/${withVersionSuffix(originalPath, versionIdString)}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Rough classifier used by `docs.delete` idempotency (§4.1 rule 4): true if
|
|
67
|
+
* ANY segment of the path starts with one of the accepted system sigils.
|
|
68
|
+
* (The design-wide primitive lives in validation.pathHasSigilSegment; this
|
|
69
|
+
* shim keeps the naming intent-facing at the call site.)
|
|
70
|
+
*/
|
|
71
|
+
export function pathIsInSystemNamespace(path, systemSigils) {
|
|
72
|
+
if (path === "")
|
|
73
|
+
return false;
|
|
74
|
+
const segments = path.split(PATH_SEPARATOR);
|
|
75
|
+
for (const segment of segments) {
|
|
76
|
+
for (const sigil of systemSigils) {
|
|
77
|
+
if (segment.startsWith(sigil))
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=deletion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deletion.js","sourceRoot":"","sources":["../../src/kernel/deletion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,YAAoB;IAIjD,wEAAwE;IACxE,uCAAuC;IACvC,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1C,IAAI,GAAG,GAAG,UAAU,IAAI,GAAG,KAAK,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,yEAAyE;QACzE,qEAAqE;QACrE,wDAAwD;QACxD,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,GAAG,GAAG,UAAU,EAAE,CAAC;YACnC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;QACnD,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IACtF,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;AACtF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,eAAuB;IACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACjD,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACrD,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,QAAQ,IAAI,eAAe,GAAG,SAAS,EAAE,CAAC;IAC7E,OAAO,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,WAAmB,EACnB,YAAoB,EACpB,eAAuB;IAEvB,OAAO,GAAG,WAAW,WAAW,iBAAiB,CAAC,YAAY,EAAE,eAAe,CAAC,EAAE,CAAC;AACrF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY,EAAE,YAA+B;IACnF,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC5C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;YACjC,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;QAC7C,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* docs.diff — the one v1 read primitive deferred from M3 (m4-plan WS5).
|
|
3
|
+
*
|
|
4
|
+
* Produces a unified diff over the byte-exact serialized documents (§3.2
|
|
5
|
+
* — one canonical representation via frontmatter.join). Both versions
|
|
6
|
+
* must belong to the same document at `(repo, path)`; otherwise the
|
|
7
|
+
* `version_not_in_document` error we reserved back in M1 finally fires.
|
|
8
|
+
*
|
|
9
|
+
* Read scope is checked on the lookup path AND both version paths
|
|
10
|
+
* (m4-plan §5 decision 10) — a doc renamed out of the caller's scope
|
|
11
|
+
* doesn't leak history through diff.
|
|
12
|
+
*/
|
|
13
|
+
import type { Storage } from "../storage/types.js";
|
|
14
|
+
import { KernelError } from "./errors.js";
|
|
15
|
+
export type UnifiedDiff = {
|
|
16
|
+
repo: string;
|
|
17
|
+
path: string;
|
|
18
|
+
from_version_id: string;
|
|
19
|
+
to_version_id: string;
|
|
20
|
+
patch: string;
|
|
21
|
+
};
|
|
22
|
+
export type DiffDeps = {
|
|
23
|
+
storage: Storage;
|
|
24
|
+
resolveReadRepo: (slug: string) => Promise<{
|
|
25
|
+
id: number;
|
|
26
|
+
slug: string;
|
|
27
|
+
}>;
|
|
28
|
+
authorizeReadPath: (repoSlug: string, path: string) => void;
|
|
29
|
+
};
|
|
30
|
+
export declare function runDiff(input: {
|
|
31
|
+
repo: string;
|
|
32
|
+
path: string;
|
|
33
|
+
from_version_id: string;
|
|
34
|
+
to_version_id: string;
|
|
35
|
+
}, deps: DiffDeps): Promise<UnifiedDiff>;
|
|
36
|
+
export { KernelError };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* docs.diff — the one v1 read primitive deferred from M3 (m4-plan WS5).
|
|
3
|
+
*
|
|
4
|
+
* Produces a unified diff over the byte-exact serialized documents (§3.2
|
|
5
|
+
* — one canonical representation via frontmatter.join). Both versions
|
|
6
|
+
* must belong to the same document at `(repo, path)`; otherwise the
|
|
7
|
+
* `version_not_in_document` error we reserved back in M1 finally fires.
|
|
8
|
+
*
|
|
9
|
+
* Read scope is checked on the lookup path AND both version paths
|
|
10
|
+
* (m4-plan §5 decision 10) — a doc renamed out of the caller's scope
|
|
11
|
+
* doesn't leak history through diff.
|
|
12
|
+
*/
|
|
13
|
+
import { createTwoFilesPatch } from "diff";
|
|
14
|
+
import { join as joinFrontmatter } from "../markdown/frontmatter.js";
|
|
15
|
+
import { KernelError, docNotFound, versionNotFound, versionNotInDocument } from "./errors.js";
|
|
16
|
+
import { decodeVersionId } from "./version-id.js";
|
|
17
|
+
export async function runDiff(input, deps) {
|
|
18
|
+
const repo = await deps.resolveReadRepo(input.repo);
|
|
19
|
+
deps.authorizeReadPath(repo.slug, input.path);
|
|
20
|
+
const current = await deps.storage.version_current(repo.id, input.path);
|
|
21
|
+
if (!current)
|
|
22
|
+
throw docNotFound(input.repo, input.path);
|
|
23
|
+
const documentId = current.document_id;
|
|
24
|
+
const from = await decodeAndFetch(deps.storage, input.from_version_id);
|
|
25
|
+
const to = await decodeAndFetch(deps.storage, input.to_version_id);
|
|
26
|
+
if (from.document_id !== documentId) {
|
|
27
|
+
throw versionNotInDocument(input.from_version_id, input.repo, input.path);
|
|
28
|
+
}
|
|
29
|
+
if (to.document_id !== documentId) {
|
|
30
|
+
throw versionNotInDocument(input.to_version_id, input.repo, input.path);
|
|
31
|
+
}
|
|
32
|
+
if (from.repo_id !== repo.id || to.repo_id !== repo.id) {
|
|
33
|
+
throw versionNotInDocument(input.from_version_id, input.repo, input.path);
|
|
34
|
+
}
|
|
35
|
+
deps.authorizeReadPath(repo.slug, from.path);
|
|
36
|
+
deps.authorizeReadPath(repo.slug, to.path);
|
|
37
|
+
const fromText = serializeVersion(from);
|
|
38
|
+
const toText = serializeVersion(to);
|
|
39
|
+
const patch = createTwoFilesPatch(`${from.path}@${input.from_version_id}`, `${to.path}@${input.to_version_id}`, fromText, toText);
|
|
40
|
+
return {
|
|
41
|
+
repo: input.repo,
|
|
42
|
+
path: input.path,
|
|
43
|
+
from_version_id: input.from_version_id,
|
|
44
|
+
to_version_id: input.to_version_id,
|
|
45
|
+
patch,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async function decodeAndFetch(storage, versionId) {
|
|
49
|
+
const id = decodeVersionId(versionId);
|
|
50
|
+
if (id === null)
|
|
51
|
+
throw versionNotFound(versionId);
|
|
52
|
+
const row = await storage.version_by_id(id);
|
|
53
|
+
if (!row)
|
|
54
|
+
throw versionNotFound(versionId);
|
|
55
|
+
return row;
|
|
56
|
+
}
|
|
57
|
+
function serializeVersion(v) {
|
|
58
|
+
return joinFrontmatter({ frontmatter_raw: v.frontmatter_raw, body: v.body });
|
|
59
|
+
}
|
|
60
|
+
export { KernelError };
|
|
61
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/kernel/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel error catalog. Every kernel-emitted error has a stable `code` and
|
|
3
|
+
* a JSON-serializable `data` payload — design §4.3.
|
|
4
|
+
*
|
|
5
|
+
* M0 surfaces only the read-time subset; writes and auth land in M1.
|
|
6
|
+
*/
|
|
7
|
+
export type KernelErrorCode = "repo_not_found" | "doc_not_found" | "version_not_found" | "version_not_in_document" | "slug_invalid" | "path_invalid" | "frontmatter_invalid" | "filter_invalid" | "link_config_invalid" | "stale_prev" | "create_conflict" | "path_taken" | "slug_taken" | "forbidden" | "precondition_required" | "payload_too_large" | "semantic_unavailable";
|
|
8
|
+
export declare class KernelError<D = Record<string, unknown>> extends Error {
|
|
9
|
+
readonly code: KernelErrorCode;
|
|
10
|
+
readonly data: D;
|
|
11
|
+
constructor(code: KernelErrorCode, data: D, message?: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Runtime set matching KernelErrorCode. Used by the remote MCP client to
|
|
15
|
+
* validate an incoming `code` string before rehydrating a KernelError —
|
|
16
|
+
* otherwise a rogue server could inject unknown codes and break clients
|
|
17
|
+
* that switch exhaustively.
|
|
18
|
+
*/
|
|
19
|
+
export declare const KERNEL_ERROR_CODES: ReadonlySet<KernelErrorCode>;
|
|
20
|
+
/** Type guard — check whether an arbitrary string is a known catalog code. */
|
|
21
|
+
export declare function isKernelErrorCode(code: string): code is KernelErrorCode;
|
|
22
|
+
export declare const repoNotFound: (slug: string) => KernelError<{
|
|
23
|
+
slug: string;
|
|
24
|
+
}>;
|
|
25
|
+
export declare const docNotFound: (repo: string, path: string) => KernelError<{
|
|
26
|
+
repo: string;
|
|
27
|
+
path: string;
|
|
28
|
+
}>;
|
|
29
|
+
export declare const versionNotFound: (versionId: string) => KernelError<{
|
|
30
|
+
version_id: string;
|
|
31
|
+
}>;
|
|
32
|
+
export declare const versionNotInDocument: (versionId: string, repo: string, path: string) => KernelError<{
|
|
33
|
+
version_id: string;
|
|
34
|
+
repo: string;
|
|
35
|
+
path: string;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const forbidden: () => KernelError<{}>;
|