@xemahq/kernel-contracts 0.50.0 → 0.57.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/dist/biome/lib/biome-manifest.d.ts +9 -0
- package/dist/biome/lib/biome-manifest.d.ts.map +1 -1
- package/dist/biome/lib/biome-manifest.js +2 -0
- package/dist/biome/lib/biome-manifest.js.map +1 -1
- package/dist/capability/index.d.ts +3 -1
- package/dist/capability/index.d.ts.map +1 -1
- package/dist/capability/index.js +3 -1
- package/dist/capability/index.js.map +1 -1
- package/dist/capability/lib/authority-effect.d.ts +8 -0
- package/dist/capability/lib/authority-effect.d.ts.map +1 -0
- package/dist/capability/lib/authority-effect.js +17 -0
- package/dist/capability/lib/authority-effect.js.map +1 -0
- package/dist/capability/lib/capability-contribution.d.ts +4 -0
- package/dist/capability/lib/capability-contribution.d.ts.map +1 -1
- package/dist/capability/lib/capability-contribution.js +4 -0
- package/dist/capability/lib/capability-contribution.js.map +1 -1
- package/dist/capability/lib/capability-domain.d.ts +8 -0
- package/dist/capability/lib/capability-domain.d.ts.map +1 -0
- package/dist/capability/lib/capability-domain.js +24 -0
- package/dist/capability/lib/capability-domain.js.map +1 -0
- package/dist/capability/lib/capability-reach.d.ts +13 -0
- package/dist/capability/lib/capability-reach.d.ts.map +1 -0
- package/dist/capability/lib/capability-reach.js +34 -0
- package/dist/capability/lib/capability-reach.js.map +1 -0
- package/dist/capability/lib/meta-tool.d.ts +51 -14
- package/dist/capability/lib/meta-tool.d.ts.map +1 -1
- package/dist/capability/lib/meta-tool.js +66 -12
- package/dist/capability/lib/meta-tool.js.map +1 -1
- package/dist/capability-projection/index.d.ts +4 -0
- package/dist/capability-projection/index.d.ts.map +1 -0
- package/dist/capability-projection/index.js +20 -0
- package/dist/capability-projection/index.js.map +1 -0
- package/dist/capability-projection/lib/capability-invocation-binding.d.ts +72 -0
- package/dist/capability-projection/lib/capability-invocation-binding.d.ts.map +1 -0
- package/dist/capability-projection/lib/capability-invocation-binding.js +101 -0
- package/dist/capability-projection/lib/capability-invocation-binding.js.map +1 -0
- package/dist/capability-projection/lib/capability-sync-manifest.d.ts +8 -0
- package/dist/capability-projection/lib/capability-sync-manifest.d.ts.map +1 -0
- package/dist/capability-projection/lib/capability-sync-manifest.js +8 -0
- package/dist/capability-projection/lib/capability-sync-manifest.js.map +1 -0
- package/dist/capability-projection/lib/projected-capability.d.ts +15 -0
- package/dist/capability-projection/lib/projected-capability.d.ts.map +1 -0
- package/dist/capability-projection/lib/projected-capability.js +17 -0
- package/dist/capability-projection/lib/projected-capability.js.map +1 -0
- package/dist/connector/lib/connector-adapter-contribution.d.ts +2 -0
- package/dist/connector/lib/connector-adapter-contribution.d.ts.map +1 -1
- package/dist/connector/lib/onboarding-manifest.d.ts +2 -0
- package/dist/connector/lib/onboarding-manifest.d.ts.map +1 -1
- package/dist/connector/lib/onboarding-manifest.js +1 -0
- package/dist/connector/lib/onboarding-manifest.js.map +1 -1
- package/dist/connector/lib/provider-descriptor.d.ts +2 -0
- package/dist/connector/lib/provider-descriptor.d.ts.map +1 -1
- package/dist/contribution/lib/contribution-kind.d.ts +1 -6
- package/dist/contribution/lib/contribution-kind.d.ts.map +1 -1
- package/dist/contribution/lib/contribution-kind.js +1 -6
- package/dist/contribution/lib/contribution-kind.js.map +1 -1
- package/dist/distribution/index.d.ts +1 -0
- package/dist/distribution/index.d.ts.map +1 -1
- package/dist/distribution/index.js +1 -0
- package/dist/distribution/index.js.map +1 -1
- package/dist/distribution/lib/distribution-lock.d.ts +6 -0
- package/dist/distribution/lib/distribution-lock.d.ts.map +1 -1
- package/dist/distribution/lib/distribution-lock.js +9 -1
- package/dist/distribution/lib/distribution-lock.js.map +1 -1
- package/dist/distribution/lib/first-party-capability-domains.d.ts +15 -0
- package/dist/distribution/lib/first-party-capability-domains.d.ts.map +1 -0
- package/dist/distribution/lib/first-party-capability-domains.js +52 -0
- package/dist/distribution/lib/first-party-capability-domains.js.map +1 -0
- package/dist/object/lib/xema-object-ref.d.ts +0 -5
- package/dist/object/lib/xema-object-ref.d.ts.map +1 -1
- package/dist/object/lib/xema-object-ref.js +12 -62
- package/dist/object/lib/xema-object-ref.js.map +1 -1
- package/dist/output-surface-inspect/index.d.ts +1 -0
- package/dist/output-surface-inspect/index.d.ts.map +1 -1
- package/dist/output-surface-inspect/index.js +1 -0
- package/dist/output-surface-inspect/index.js.map +1 -1
- package/dist/output-surface-inspect/lib/buffers.d.ts +52 -0
- package/dist/output-surface-inspect/lib/buffers.d.ts.map +1 -0
- package/dist/output-surface-inspect/lib/buffers.js +132 -0
- package/dist/output-surface-inspect/lib/buffers.js.map +1 -0
- package/dist/output-surface-inspect/lib/output-surface-inspect.d.ts +6 -1
- package/dist/output-surface-inspect/lib/output-surface-inspect.d.ts.map +1 -1
- package/dist/output-surface-inspect/lib/output-surface-inspect.js +12 -1
- package/dist/output-surface-inspect/lib/output-surface-inspect.js.map +1 -1
- package/dist/output-surface-inspect/lib/step-schema.d.ts +2 -1
- package/dist/output-surface-inspect/lib/step-schema.d.ts.map +1 -1
- package/dist/output-surface-inspect/lib/step-schema.js +9 -2
- package/dist/output-surface-inspect/lib/step-schema.js.map +1 -1
- package/dist/output-surface-inspect/runtime/index.d.ts +1 -0
- package/dist/output-surface-inspect/runtime/index.d.ts.map +1 -1
- package/dist/output-surface-inspect/runtime/index.js +1 -0
- package/dist/output-surface-inspect/runtime/index.js.map +1 -1
- package/dist/search/index.d.ts +1 -0
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +1 -0
- package/dist/search/index.js.map +1 -1
- package/dist/search/lib/route-template.d.ts +31 -0
- package/dist/search/lib/route-template.d.ts.map +1 -0
- package/dist/search/lib/route-template.js +142 -0
- package/dist/search/lib/route-template.js.map +1 -0
- package/dist/search/lib/search-contribution.d.ts +4 -2
- package/dist/search/lib/search-contribution.d.ts.map +1 -1
- package/dist/search/lib/search-contribution.js +46 -4
- package/dist/search/lib/search-contribution.js.map +1 -1
- package/dist/search-source/lib/indexable-document.d.ts +22 -0
- package/dist/search-source/lib/indexable-document.d.ts.map +1 -1
- package/dist/search-source/lib/indexable-document.js +10 -1
- package/dist/search-source/lib/indexable-document.js.map +1 -1
- package/dist/search-source/lib/search-index-event.d.ts +10 -0
- package/dist/search-source/lib/search-index-event.d.ts.map +1 -1
- package/dist/search-source/lib/search-ref.d.ts.map +1 -1
- package/dist/search-source/lib/search-ref.js +11 -2
- package/dist/search-source/lib/search-ref.js.map +1 -1
- package/dist/search-source/lib/search-replay.d.ts +10 -0
- package/dist/search-source/lib/search-replay.d.ts.map +1 -1
- package/dist/service-registry/lib/service-descriptor.d.ts +1 -0
- package/dist/service-registry/lib/service-descriptor.d.ts.map +1 -1
- package/dist/space/lib/space-ref-parser.d.ts +6 -4
- package/dist/space/lib/space-ref-parser.d.ts.map +1 -1
- package/dist/space/lib/space-ref-parser.js +90 -126
- package/dist/space/lib/space-ref-parser.js.map +1 -1
- package/dist/space/lib/space-traversal.d.ts.map +1 -1
- package/dist/space/lib/space-traversal.js +9 -13
- package/dist/space/lib/space-traversal.js.map +1 -1
- package/dist/space/lib/space.d.ts +7 -0
- package/dist/space/lib/space.d.ts.map +1 -1
- package/dist/space/lib/space.js +48 -29
- package/dist/space/lib/space.js.map +1 -1
- package/dist/subject/lib/subject.d.ts +1 -0
- package/dist/subject/lib/subject.d.ts.map +1 -1
- package/dist/subject/lib/subject.js +1 -0
- package/dist/subject/lib/subject.js.map +1 -1
- package/dist/surface/index.d.ts +4 -0
- package/dist/surface/index.d.ts.map +1 -0
- package/dist/surface/index.js +20 -0
- package/dist/surface/index.js.map +1 -0
- package/dist/surface/lib/surface-contribution.d.ts +24 -0
- package/dist/surface/lib/surface-contribution.d.ts.map +1 -0
- package/dist/surface/lib/surface-contribution.js +23 -0
- package/dist/surface/lib/surface-contribution.js.map +1 -0
- package/dist/surface/lib/surface-kind.d.ts +23 -0
- package/dist/surface/lib/surface-kind.d.ts.map +1 -0
- package/dist/surface/lib/surface-kind.js +27 -0
- package/dist/surface/lib/surface-kind.js.map +1 -0
- package/dist/surface/lib/vista-surface.d.ts +11 -0
- package/dist/surface/lib/vista-surface.d.ts.map +1 -0
- package/dist/surface/lib/vista-surface.js +13 -0
- package/dist/surface/lib/vista-surface.js.map +1 -0
- package/dist/widget/lib/chat-widget-envelope.d.ts.map +1 -1
- package/dist/widget/lib/chat-widget-envelope.js.map +1 -1
- package/dist/workflow/lib/enums.d.ts.map +1 -1
- package/dist/workflow/lib/enums.js.map +1 -1
- package/package.json +1 -1
- package/src/biome/lib/biome-manifest.ts +41 -1
- package/src/capability/index.ts +3 -1
- package/src/capability/lib/authority-effect.ts +88 -0
- package/src/capability/lib/capability-contribution.ts +44 -0
- package/src/capability/lib/capability-domain.ts +51 -0
- package/src/capability/lib/capability-grant.ts +1 -1
- package/src/capability/lib/capability-reach.ts +119 -0
- package/src/capability/lib/meta-tool.ts +172 -39
- package/src/capability-projection/index.ts +3 -0
- package/src/capability-projection/lib/capability-invocation-binding.ts +223 -0
- package/src/capability-projection/lib/capability-sync-manifest.ts +52 -0
- package/src/capability-projection/lib/projected-capability.ts +63 -0
- package/src/connector/lib/onboarding-manifest.ts +16 -0
- package/src/contribution/lib/contribution-kind.ts +165 -61
- package/src/distribution/index.ts +1 -0
- package/src/distribution/lib/distribution-lock.ts +36 -0
- package/src/distribution/lib/first-party-capability-domains.ts +166 -0
- package/src/object/lib/xema-object-ref.ts +27 -82
- package/src/output-surface-inspect/index.ts +2 -0
- package/src/output-surface-inspect/lib/buffers.ts +252 -0
- package/src/output-surface-inspect/lib/output-surface-inspect.ts +65 -6
- package/src/output-surface-inspect/lib/step-schema.ts +36 -3
- package/src/output-surface-inspect/runtime/index.ts +1 -0
- package/src/search/index.ts +1 -0
- package/src/search/lib/route-template.ts +381 -0
- package/src/search/lib/search-contribution.ts +138 -31
- package/src/search-source/lib/indexable-document.ts +44 -0
- package/src/search-source/lib/search-ref.ts +22 -9
- package/src/service-registry/lib/service-descriptor.ts +16 -0
- package/src/space/lib/skill-mirror-path.ts +1 -1
- package/src/space/lib/space-ref-parser.ts +190 -165
- package/src/space/lib/space-traversal.ts +34 -17
- package/src/space/lib/space.ts +110 -35
- package/src/subject/lib/subject.ts +16 -0
- package/src/surface/index.ts +3 -0
- package/src/surface/lib/surface-contribution.ts +101 -0
- package/src/surface/lib/surface-kind.ts +65 -0
- package/src/surface/lib/vista-surface.ts +44 -0
- package/src/widget/lib/chat-widget-envelope.ts +6 -2
- package/src/workflow/lib/enums.ts +6 -7
- package/src/capability/lib/first-party-capability-domains.ts +0 -77
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { type XemaObjectRef } from '../../object';
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
parseSpaceRefScopeSegments,
|
|
4
|
+
SpaceKind,
|
|
5
|
+
SpaceRefParseError,
|
|
6
|
+
spaceRefToPath,
|
|
7
|
+
type SpaceRef,
|
|
8
|
+
} from '../../space';
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* The natural-key / address grammar for a searchable document:
|
|
@@ -15,9 +18,10 @@ import { SpaceKind, spaceRefToPath, type SpaceRef } from '../../space';
|
|
|
15
18
|
* the type discriminator is `docType` (biome-owned, open), so a searchable
|
|
16
19
|
* document (an email, a design-idea, a backlog item) can be addressed WITHOUT a
|
|
17
20
|
* kernel object kind. The scope grammar itself is shared: this helper reuses
|
|
18
|
-
* {@link
|
|
19
|
-
* `<scope-path>` portion stays value-identical to `XemaObjectRef
|
|
20
|
-
* model ref validation is untouched for object-model
|
|
21
|
+
* {@link parseSpaceRefScopeSegments} + {@link spaceRefToPath} so the
|
|
22
|
+
* `<scope-path>` portion stays value-identical to `XemaObjectRef` and to a
|
|
23
|
+
* Space ref, and object-model ref validation is untouched for object-model
|
|
24
|
+
* callers.
|
|
21
25
|
*
|
|
22
26
|
* `docType` occupies the segment where an object ref carries `<kind>`; it is a
|
|
23
27
|
* lowercase, slash-free facet slug (e.g. `kb_page`, `email_thread`). `sourceId`
|
|
@@ -96,7 +100,7 @@ export function formatSearchRef(
|
|
|
96
100
|
/**
|
|
97
101
|
* Parse a search ref back into `{ scope, docType, sourceId }`. Fail-fast on
|
|
98
102
|
* every malformed input. The scope grammar is validated by the shared
|
|
99
|
-
* {@link
|
|
103
|
+
* {@link parseSpaceRefScopeSegments}; the trailing two segments are the
|
|
100
104
|
* `docType` and the (slash-free) `sourceId`.
|
|
101
105
|
*/
|
|
102
106
|
export function parseSearchRef(ref: string): SearchRefParts {
|
|
@@ -115,7 +119,16 @@ export function parseSearchRef(ref: string): SearchRefParts {
|
|
|
115
119
|
);
|
|
116
120
|
}
|
|
117
121
|
|
|
118
|
-
|
|
122
|
+
let scope: SpaceRef;
|
|
123
|
+
let consumed: number;
|
|
124
|
+
try {
|
|
125
|
+
({ scope, consumed } = parseSpaceRefScopeSegments(segments, ref));
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (err instanceof SpaceRefParseError) {
|
|
128
|
+
throw new SearchRefParseError(ref, err.message);
|
|
129
|
+
}
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
119
132
|
const remaining = segments.slice(consumed);
|
|
120
133
|
if (remaining.length !== 2) {
|
|
121
134
|
throw new SearchRefParseError(
|
|
@@ -61,6 +61,11 @@ export interface ServiceEndpoint {
|
|
|
61
61
|
* resolved and must not register.
|
|
62
62
|
* - `exposesCapabilities` / `requiresServices` carry only canonical
|
|
63
63
|
* refs / service names — no env-var URLs.
|
|
64
|
+
* - `openapiUrl`, when present, is ABSOLUTE and points at the service's
|
|
65
|
+
* PUBLIC-surface OpenAPI document. It is declared, never derived by
|
|
66
|
+
* convention: a consumer must not guess `<endpoint>/api/openapi.json`,
|
|
67
|
+
* because that path serves the merged public+internal+operator document and
|
|
68
|
+
* because a service may mount its docs elsewhere or not at all.
|
|
64
69
|
*/
|
|
65
70
|
export interface ServiceDescriptor {
|
|
66
71
|
name: string;
|
|
@@ -70,6 +75,17 @@ export interface ServiceDescriptor {
|
|
|
70
75
|
exposesCapabilities: string[];
|
|
71
76
|
requiresServices: string[];
|
|
72
77
|
healthEndpoint?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Absolute URL of the service's PUBLIC-surface OpenAPI document (the same
|
|
80
|
+
* subset shipped as the `openapi.public.json` build artifact).
|
|
81
|
+
*
|
|
82
|
+
* Optional because a service MAY serve no spec — Swagger can be disabled per
|
|
83
|
+
* deployment, and a non-HTTP service has none at all. Absent means exactly
|
|
84
|
+
* "this instance advertises no machine-readable API description"; consumers
|
|
85
|
+
* MUST treat absence as a hard skip and never fall back to a conventional
|
|
86
|
+
* path.
|
|
87
|
+
*/
|
|
88
|
+
openapiUrl?: string;
|
|
73
89
|
region?: string;
|
|
74
90
|
labels?: Record<string, string>;
|
|
75
91
|
}
|
|
@@ -34,7 +34,7 @@ const SKILL_KIND_SEGMENT = 'skill';
|
|
|
34
34
|
* the org's own RO mount; another org's worker never mounts it.
|
|
35
35
|
*
|
|
36
36
|
* Returns `null` when the scope is not mirrorable from the ref alone:
|
|
37
|
-
* • User — the ref is org-less (`xema://
|
|
37
|
+
* • User — the ref is org-less (`xema://users/<userId>`), so it can't be
|
|
38
38
|
* placed under any org's templates subtree.
|
|
39
39
|
* • App / Session — runtime spaces, never own a skill bundle.
|
|
40
40
|
* • a mirrorable tier whose required id segment is absent (malformed ref).
|
|
@@ -1,198 +1,223 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
assertValidSpaceSegmentId,
|
|
3
|
+
SpaceKind,
|
|
4
|
+
SpaceRefParseError,
|
|
5
|
+
spaceRefToPath,
|
|
6
|
+
type SpaceRef,
|
|
7
|
+
} from './space';
|
|
8
|
+
|
|
9
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
10
|
+
// THE Space URI grammar. ONE grammar, platform-wide.
|
|
4
11
|
//
|
|
5
12
|
// xema://system
|
|
6
|
-
// xema://
|
|
7
|
-
// xema://
|
|
8
|
-
// xema://
|
|
9
|
-
// xema://
|
|
10
|
-
// xema://
|
|
11
|
-
// xema://
|
|
13
|
+
// xema://users/<userId>
|
|
14
|
+
// xema://biomes/<biomeId>
|
|
15
|
+
// xema://sessions/<sessionId>
|
|
16
|
+
// xema://orgs/<orgId>
|
|
17
|
+
// xema://orgs/<orgId>/projects/<projectId>
|
|
18
|
+
// xema://orgs/<orgId>/projects/<projectId>/apps/<appId>
|
|
19
|
+
//
|
|
20
|
+
// Historical note (X9 convergence): this module used to implement a SECOND,
|
|
21
|
+
// incompatible grammar — singular `org`/`project`/`app` key/id pairs that
|
|
22
|
+
// additionally NESTED session and biome under app
|
|
23
|
+
// (`xema://org/a/project/p/app/x/session/y`). It disagreed with
|
|
24
|
+
// `spaceRefToPath` (the plural form that `XemaObjectRef`, search refs and the
|
|
25
|
+
// object-registry projection all use) for the Biome, Session and User tiers,
|
|
26
|
+
// so `formatSpaceRef` and `spaceRefToPath` were not inverses and a ref could
|
|
27
|
+
// not survive a round trip through both. The singular grammar LOST and was
|
|
28
|
+
// deleted outright — no shim, no dual-read. Reasons, in order of weight:
|
|
12
29
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
30
|
+
// 1. `REQUIRED_SEGMENTS` in `./space` already declares Session/Biome/User as
|
|
31
|
+
// root-addressable (they require ONLY their own id). The singular parser
|
|
32
|
+
// contradicted the kernel's own SpaceRef schema.
|
|
33
|
+
// 2. Every real producer of a Biome- or Session-scoped ref in the platform
|
|
34
|
+
// (skill-registry, llm-registry, artifact-store, workflow-engine, xvfs)
|
|
35
|
+
// constructs `{ tier, biomeId }` / `{ tier, sessionId }` with no org,
|
|
36
|
+
// project or app. `formatSpaceRef` THREW on all of them.
|
|
37
|
+
// 3. The plural grammar was already the persisted/wire form for object refs,
|
|
38
|
+
// search refs and XVFS scope paths; only Space rows used the singular one.
|
|
39
|
+
//
|
|
40
|
+
// `parse` and `format` here are exact inverses over canonical inputs, and
|
|
41
|
+
// `formatSpaceRef` is defined as `xema://` + `spaceRefToPath`, so the two
|
|
42
|
+
// renderers cannot drift again.
|
|
43
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
17
44
|
|
|
18
45
|
const SCHEME = 'xema://';
|
|
19
|
-
const IDENT_REGEX = /^[a-z0-9][a-z0-9_-]*$/;
|
|
20
46
|
|
|
21
|
-
|
|
22
|
-
|
|
47
|
+
/** Plural scope heads the grammar admits. Closed set. */
|
|
48
|
+
enum SpaceRefScopeHead {
|
|
49
|
+
System = 'system',
|
|
50
|
+
Users = 'users',
|
|
51
|
+
Biomes = 'biomes',
|
|
52
|
+
Sessions = 'sessions',
|
|
53
|
+
Orgs = 'orgs',
|
|
54
|
+
Projects = 'projects',
|
|
55
|
+
Apps = 'apps',
|
|
56
|
+
}
|
|
23
57
|
|
|
24
|
-
const
|
|
58
|
+
const SPACE_REF_SCOPE_HEADS: readonly string[] =
|
|
59
|
+
Object.values(SpaceRefScopeHead);
|
|
25
60
|
|
|
26
|
-
|
|
27
|
-
// Each chain must be a strict prefix of one canonical path.
|
|
28
|
-
const KIND_BY_DEEPEST_SEGMENT: Readonly<Record<SegmentKey, SpaceKind>> = {
|
|
29
|
-
org: SpaceKind.Org,
|
|
30
|
-
project: SpaceKind.Project,
|
|
31
|
-
app: SpaceKind.App,
|
|
32
|
-
session: SpaceKind.Session,
|
|
33
|
-
biome: SpaceKind.Biome,
|
|
34
|
-
user: SpaceKind.User,
|
|
35
|
-
};
|
|
61
|
+
export { SpaceRefParseError };
|
|
36
62
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
/** Result of consuming the leading scope segments of a `xema://` path. */
|
|
64
|
+
export interface SpaceRefScopeParse {
|
|
65
|
+
scope: SpaceRef;
|
|
66
|
+
consumed: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function requireSegment(
|
|
70
|
+
segments: readonly string[],
|
|
71
|
+
index: number,
|
|
72
|
+
raw: string,
|
|
73
|
+
label: string,
|
|
74
|
+
): string {
|
|
75
|
+
const value = segments[index];
|
|
76
|
+
if (value === undefined || value.length === 0) {
|
|
77
|
+
throw new SpaceRefParseError(
|
|
78
|
+
`SpaceRef URI is missing the "${label}" segment.`,
|
|
79
|
+
raw,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
46
84
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
85
|
+
function takeId(
|
|
86
|
+
segments: readonly string[],
|
|
87
|
+
index: number,
|
|
88
|
+
raw: string,
|
|
89
|
+
field: keyof SpaceRef,
|
|
90
|
+
): string {
|
|
91
|
+
const value = requireSegment(segments, index, raw, String(field));
|
|
92
|
+
assertValidSpaceSegmentId(field, value, raw);
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
56
95
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Consume the LEADING scope segments of a `xema://` path body and return the
|
|
98
|
+
* {@link SpaceRef} they denote plus how many segments were eaten. Shared by
|
|
99
|
+
* {@link parseSpaceRef} (which additionally requires the scope to consume the
|
|
100
|
+
* WHOLE body), `parseXemaObjectRef` (which owns the trailing `<kind>/<slug>`)
|
|
101
|
+
* and `parseSearchRef` (which owns the trailing `<docType>/<sourceId>`), so
|
|
102
|
+
* the scope grammar lives in exactly ONE place.
|
|
103
|
+
*/
|
|
104
|
+
export function parseSpaceRefScopeSegments(
|
|
105
|
+
segments: readonly string[],
|
|
106
|
+
raw: string,
|
|
107
|
+
): SpaceRefScopeParse {
|
|
108
|
+
const head = requireSegment(segments, 0, raw, 'scope');
|
|
109
|
+
switch (head) {
|
|
110
|
+
case SpaceRefScopeHead.System:
|
|
111
|
+
return { scope: { tier: SpaceKind.System }, consumed: 1 };
|
|
112
|
+
case SpaceRefScopeHead.Biomes:
|
|
113
|
+
return {
|
|
114
|
+
scope: {
|
|
115
|
+
tier: SpaceKind.Biome,
|
|
116
|
+
biomeId: takeId(segments, 1, raw, 'biomeId'),
|
|
117
|
+
},
|
|
118
|
+
consumed: 2,
|
|
119
|
+
};
|
|
120
|
+
case SpaceRefScopeHead.Users:
|
|
121
|
+
return {
|
|
122
|
+
scope: {
|
|
123
|
+
tier: SpaceKind.User,
|
|
124
|
+
userId: takeId(segments, 1, raw, 'userId'),
|
|
125
|
+
},
|
|
126
|
+
consumed: 2,
|
|
127
|
+
};
|
|
128
|
+
case SpaceRefScopeHead.Sessions:
|
|
129
|
+
return {
|
|
130
|
+
scope: {
|
|
131
|
+
tier: SpaceKind.Session,
|
|
132
|
+
sessionId: takeId(segments, 1, raw, 'sessionId'),
|
|
133
|
+
},
|
|
134
|
+
consumed: 2,
|
|
135
|
+
};
|
|
136
|
+
case SpaceRefScopeHead.Orgs: {
|
|
137
|
+
const orgId = takeId(segments, 1, raw, 'orgId');
|
|
138
|
+
if (segments.length >= 4 && segments[2] === SpaceRefScopeHead.Projects) {
|
|
139
|
+
const projectId = takeId(segments, 3, raw, 'projectId');
|
|
140
|
+
// …/projects/<p>/apps/<a> — the App tier (most specific under a project).
|
|
141
|
+
if (segments.length >= 6 && segments[4] === SpaceRefScopeHead.Apps) {
|
|
142
|
+
return {
|
|
143
|
+
scope: {
|
|
144
|
+
tier: SpaceKind.App,
|
|
145
|
+
orgId,
|
|
146
|
+
projectId,
|
|
147
|
+
appId: takeId(segments, 5, raw, 'appId'),
|
|
148
|
+
},
|
|
149
|
+
consumed: 6,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
scope: { tier: SpaceKind.Project, orgId, projectId },
|
|
154
|
+
consumed: 4,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return { scope: { tier: SpaceKind.Org, orgId }, consumed: 2 };
|
|
158
|
+
}
|
|
159
|
+
default:
|
|
160
|
+
throw new SpaceRefParseError(
|
|
161
|
+
`SpaceRef URI has unknown scope head "${head}". Allowed: ${SPACE_REF_SCOPE_HEADS.join(', ')}.`,
|
|
162
|
+
raw,
|
|
163
|
+
);
|
|
63
164
|
}
|
|
64
165
|
}
|
|
65
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Parse a full `xema://…` Space URI. Unlike
|
|
169
|
+
* {@link parseSpaceRefScopeSegments}, the scope MUST consume the entire body —
|
|
170
|
+
* a trailing `<kind>/<slug>` makes it an object ref, not a Space ref, and is a
|
|
171
|
+
* fail-fast error here rather than a silently-ignored suffix.
|
|
172
|
+
*
|
|
173
|
+
* The returned ref carries `path` set to the CANONICAL rendering, so
|
|
174
|
+
* `formatSpaceRef(parseSpaceRef(uri)) === uri` for every canonical `uri`.
|
|
175
|
+
*/
|
|
66
176
|
export function parseSpaceRef(uri: string): SpaceRef {
|
|
67
177
|
if (typeof uri !== 'string' || uri.length === 0) {
|
|
68
|
-
throw new SpaceRefParseError(
|
|
178
|
+
throw new SpaceRefParseError(
|
|
179
|
+
'SpaceRef URI must be a non-empty string.',
|
|
180
|
+
String(uri),
|
|
181
|
+
);
|
|
69
182
|
}
|
|
70
183
|
if (!uri.startsWith(SCHEME)) {
|
|
71
|
-
throw new SpaceRefParseError(
|
|
184
|
+
throw new SpaceRefParseError(
|
|
185
|
+
`SpaceRef URI must start with "${SCHEME}".`,
|
|
186
|
+
uri,
|
|
187
|
+
);
|
|
72
188
|
}
|
|
73
189
|
const body = uri.slice(SCHEME.length);
|
|
74
190
|
if (body.length === 0) {
|
|
75
|
-
throw new SpaceRefParseError('SpaceRef URI has empty body after scheme.', uri);
|
|
76
|
-
}
|
|
77
|
-
const parts = body.split('/');
|
|
78
|
-
|
|
79
|
-
if (parts.length === 1 && parts[0] === 'system') {
|
|
80
|
-
return { tier: SpaceKind.System, path: uri };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (parts.length % 2 !== 0) {
|
|
84
191
|
throw new SpaceRefParseError(
|
|
85
|
-
|
|
192
|
+
'SpaceRef URI has empty body after scheme.',
|
|
86
193
|
uri,
|
|
87
194
|
);
|
|
88
195
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const value = parts[i + 1]!;
|
|
96
|
-
if (!SEGMENT_KEY_SET.has(key)) {
|
|
97
|
-
throw new SpaceRefParseError(
|
|
98
|
-
`SpaceRef URI has unknown segment key "${key}". Allowed: ${SEGMENT_KEYS.join(', ')}.`,
|
|
99
|
-
uri,
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
const typedKey = key as SegmentKey;
|
|
103
|
-
if (segments[typedKey] !== undefined) {
|
|
104
|
-
throw new SpaceRefParseError(`SpaceRef URI repeats segment "${key}".`, uri);
|
|
105
|
-
}
|
|
106
|
-
if (!IDENT_REGEX.test(value)) {
|
|
107
|
-
throw new SpaceRefParseError(
|
|
108
|
-
`SpaceRef URI segment "${key}" has invalid id "${value}".`,
|
|
109
|
-
uri,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
const expectedParent = PARENT_OF[typedKey];
|
|
113
|
-
if (expectedParent !== lastSegment) {
|
|
114
|
-
throw new SpaceRefParseError(
|
|
115
|
-
`SpaceRef URI segment "${key}" must follow "${expectedParent ?? '(root)'}", got "${lastSegment ?? '(root)'}".`,
|
|
116
|
-
uri,
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
segments[typedKey] = value;
|
|
120
|
-
lastSegment = typedKey;
|
|
196
|
+
const segments = body.split('/');
|
|
197
|
+
if (segments.some((segment) => segment.length === 0)) {
|
|
198
|
+
throw new SpaceRefParseError(
|
|
199
|
+
'SpaceRef URI has an empty path segment (leading, trailing or doubled "/").',
|
|
200
|
+
uri,
|
|
201
|
+
);
|
|
121
202
|
}
|
|
122
203
|
|
|
123
|
-
|
|
124
|
-
|
|
204
|
+
const { scope, consumed } = parseSpaceRefScopeSegments(segments, uri);
|
|
205
|
+
if (consumed !== segments.length) {
|
|
206
|
+
throw new SpaceRefParseError(
|
|
207
|
+
`SpaceRef URI has ${segments.length - consumed} trailing segment(s) after the scope — a Space ref addresses a Space, not an object inside it.`,
|
|
208
|
+
uri,
|
|
209
|
+
);
|
|
125
210
|
}
|
|
126
211
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
for (const segKey of SEGMENT_KEYS) {
|
|
130
|
-
const segValue = segments[segKey];
|
|
131
|
-
if (segValue !== undefined) {
|
|
132
|
-
const fieldName = FIELD_OF[segKey];
|
|
133
|
-
(ref as unknown as Record<string, string>)[fieldName] = segValue;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return ref;
|
|
212
|
+
scope.path = formatSpaceRef(scope);
|
|
213
|
+
return scope;
|
|
137
214
|
}
|
|
138
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Render a `SpaceRef` as its canonical `xema://…` URI. Defined as the scheme
|
|
218
|
+
* plus {@link spaceRefToPath}, so the URI form and the XVFS path form can
|
|
219
|
+
* never disagree again. Fails fast on a missing or malformed segment.
|
|
220
|
+
*/
|
|
139
221
|
export function formatSpaceRef(ref: SpaceRef): string {
|
|
140
|
-
|
|
141
|
-
return `${SCHEME}system`;
|
|
142
|
-
}
|
|
143
|
-
const out: string[] = [SCHEME];
|
|
144
|
-
const append = (key: SegmentKey, value: string | undefined): void => {
|
|
145
|
-
if (value === undefined) {
|
|
146
|
-
throw new SpaceRefParseError(
|
|
147
|
-
`formatSpaceRef: missing required segment "${key}" for tier "${ref.tier}".`,
|
|
148
|
-
ref.tier,
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
if (!IDENT_REGEX.test(value)) {
|
|
152
|
-
throw new SpaceRefParseError(
|
|
153
|
-
`formatSpaceRef: segment "${key}" id "${value}" violates grammar.`,
|
|
154
|
-
ref.tier,
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
if (out.length > 1) out.push('/');
|
|
158
|
-
out.push(`${key}/${value}`);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
switch (ref.tier) {
|
|
162
|
-
case SpaceKind.User:
|
|
163
|
-
append('user', ref.userId);
|
|
164
|
-
break;
|
|
165
|
-
case SpaceKind.Org:
|
|
166
|
-
append('org', ref.orgId);
|
|
167
|
-
break;
|
|
168
|
-
case SpaceKind.Project:
|
|
169
|
-
append('org', ref.orgId);
|
|
170
|
-
append('project', ref.projectId);
|
|
171
|
-
break;
|
|
172
|
-
case SpaceKind.App:
|
|
173
|
-
append('org', ref.orgId);
|
|
174
|
-
append('project', ref.projectId);
|
|
175
|
-
append('app', ref.appId);
|
|
176
|
-
break;
|
|
177
|
-
case SpaceKind.Session:
|
|
178
|
-
append('org', ref.orgId);
|
|
179
|
-
append('project', ref.projectId);
|
|
180
|
-
append('app', ref.appId);
|
|
181
|
-
append('session', ref.sessionId);
|
|
182
|
-
break;
|
|
183
|
-
case SpaceKind.Biome:
|
|
184
|
-
append('org', ref.orgId);
|
|
185
|
-
append('project', ref.projectId);
|
|
186
|
-
append('app', ref.appId);
|
|
187
|
-
append('biome', ref.biomeId);
|
|
188
|
-
break;
|
|
189
|
-
default: {
|
|
190
|
-
const exhaustive: never = ref.tier;
|
|
191
|
-
throw new SpaceRefParseError(
|
|
192
|
-
`formatSpaceRef: unhandled SpaceKind "${String(exhaustive)}".`,
|
|
193
|
-
String(exhaustive),
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return out.join('');
|
|
222
|
+
return `${SCHEME}${spaceRefToPath(ref).slice(1)}`;
|
|
198
223
|
}
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import { formatSpaceRef } from './space-ref-parser';
|
|
2
2
|
import { SpaceKind, type SpaceRef } from './space';
|
|
3
3
|
|
|
4
|
-
// Closest-first ancestor walk.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// Closest-first ancestor walk. The chain MUST agree with the URI grammar in
|
|
5
|
+
// `./space-ref-parser` — an ancestor is exactly "this ref with its deepest
|
|
6
|
+
// segment stripped", so the walk is the structural reading of the same
|
|
7
|
+
// canonical path:
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
9
|
+
// /orgs/a/projects/p/apps/x → [App, Project, Org, System]
|
|
10
|
+
// /orgs/a/projects/p → [Project, Org, System]
|
|
11
|
+
// /sessions/s → [Session, System]
|
|
12
|
+
// /biomes/b → [Biome, System]
|
|
13
|
+
// /users/u → [User, System]
|
|
14
|
+
//
|
|
15
|
+
// Session, Biome and User are ROOTS (parent = System). Before the X9
|
|
16
|
+
// convergence this file nested Session and Biome under App while the path
|
|
17
|
+
// renderer emitted them at the root, so `walkAncestors` THREW for every
|
|
18
|
+
// Biome-scoped ref the platform actually produces (`{ tier: Biome, biomeId }`
|
|
19
|
+
// carries no appId, and rendering the synthesised App parent failed). Keeping
|
|
20
|
+
// the hierarchy and the grammar in lockstep is what makes `parentOf` total.
|
|
21
|
+
//
|
|
22
|
+
// Used by policy resolution ("most-specific match wins"), by
|
|
23
|
+
// space-registry-api to pin `Space.parentId` from `walkAncestors(ref)[1]`, and
|
|
24
|
+
// by capability-registry-api's `SPACE_KIND_PARENT` derivation — all three read
|
|
25
|
+
// the hierarchy from HERE and never restate it.
|
|
11
26
|
export function walkAncestors(ref: SpaceRef): SpaceRef[] {
|
|
12
27
|
const out: SpaceRef[] = [];
|
|
13
28
|
let current: SpaceRef | null = ref;
|
|
@@ -18,13 +33,22 @@ export function walkAncestors(ref: SpaceRef): SpaceRef[] {
|
|
|
18
33
|
return out;
|
|
19
34
|
}
|
|
20
35
|
|
|
36
|
+
function systemParent(): SpaceRef {
|
|
37
|
+
return {
|
|
38
|
+
tier: SpaceKind.System,
|
|
39
|
+
path: formatSpaceRef({ tier: SpaceKind.System }),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
21
43
|
function parentOf(ref: SpaceRef): SpaceRef | null {
|
|
22
44
|
switch (ref.tier) {
|
|
23
45
|
case SpaceKind.System:
|
|
24
46
|
return null;
|
|
25
47
|
case SpaceKind.User:
|
|
26
48
|
case SpaceKind.Org:
|
|
27
|
-
|
|
49
|
+
case SpaceKind.Session:
|
|
50
|
+
case SpaceKind.Biome:
|
|
51
|
+
return systemParent();
|
|
28
52
|
case SpaceKind.Project: {
|
|
29
53
|
const parent: SpaceRef = { tier: SpaceKind.Org };
|
|
30
54
|
if (ref.orgId !== undefined) parent.orgId = ref.orgId;
|
|
@@ -38,18 +62,11 @@ function parentOf(ref: SpaceRef): SpaceRef | null {
|
|
|
38
62
|
parent.path = formatSpaceRef(parent);
|
|
39
63
|
return parent;
|
|
40
64
|
}
|
|
41
|
-
case SpaceKind.Session:
|
|
42
|
-
case SpaceKind.Biome: {
|
|
43
|
-
const parent: SpaceRef = { tier: SpaceKind.App };
|
|
44
|
-
if (ref.orgId !== undefined) parent.orgId = ref.orgId;
|
|
45
|
-
if (ref.projectId !== undefined) parent.projectId = ref.projectId;
|
|
46
|
-
if (ref.appId !== undefined) parent.appId = ref.appId;
|
|
47
|
-
parent.path = formatSpaceRef(parent);
|
|
48
|
-
return parent;
|
|
49
|
-
}
|
|
50
65
|
default: {
|
|
51
66
|
const exhaustive: never = ref.tier;
|
|
52
|
-
throw new Error(
|
|
67
|
+
throw new Error(
|
|
68
|
+
`walkAncestors: unhandled SpaceKind "${String(exhaustive)}".`,
|
|
69
|
+
);
|
|
53
70
|
}
|
|
54
71
|
}
|
|
55
72
|
}
|