@xemahq/kernel-contracts 0.50.0 → 0.56.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 +2 -1
- package/dist/capability/index.d.ts.map +1 -1
- package/dist/capability/index.js +2 -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 +2 -0
- package/dist/capability/lib/capability-contribution.d.ts.map +1 -1
- package/dist/capability/lib/capability-contribution.js +2 -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/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 -4
- package/dist/contribution/lib/contribution-kind.d.ts.map +1 -1
- package/dist/contribution/lib/contribution-kind.js +1 -4
- 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 +2 -0
- package/dist/search/lib/search-contribution.d.ts.map +1 -1
- package/dist/search/lib/search-contribution.js +34 -1
- 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 +2 -1
- package/src/capability/lib/authority-effect.ts +88 -0
- package/src/capability/lib/capability-contribution.ts +21 -0
- package/src/capability/lib/capability-domain.ts +51 -0
- package/src/capability/lib/capability-grant.ts +1 -1
- 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 +30 -29
- 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 +84 -11
- 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/dist/capability/lib/first-party-capability-domains.d.ts +0 -30
- package/dist/capability/lib/first-party-capability-domains.d.ts.map +0 -1
- package/dist/capability/lib/first-party-capability-domains.js +0 -36
- package/dist/capability/lib/first-party-capability-domains.js.map +0 -1
- package/src/capability/lib/first-party-capability-domains.ts +0 -77
|
@@ -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
|
}
|
package/src/space/lib/space.ts
CHANGED
|
@@ -148,48 +148,123 @@ const REQUIRED_SEGMENTS: Readonly<Record<SpaceKind, readonly SegmentKey[]>> = {
|
|
|
148
148
|
};
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
151
|
+
* Structured, fail-fast error raised by every Space-ref grammar operation
|
|
152
|
+
* ({@link spaceRefToPath}, `parseSpaceRef`, `formatSpaceRef`). Carries a
|
|
153
|
+
* stable `code` so services can map it to a typed 400 without string-matching
|
|
154
|
+
* the message.
|
|
155
|
+
*/
|
|
156
|
+
export class SpaceRefParseError extends Error {
|
|
157
|
+
public readonly code = 'SPACE_REF_INVALID';
|
|
158
|
+
public readonly raw: string;
|
|
159
|
+
|
|
160
|
+
public constructor(message: string, raw: string) {
|
|
161
|
+
super(message);
|
|
162
|
+
this.name = 'SpaceRefParseError';
|
|
163
|
+
this.raw = raw;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A single Space path segment id (an orgId, projectId, appId, sessionId,
|
|
169
|
+
* biomeId or userId). Deliberately value-identical to the `XemaObjectRef`
|
|
170
|
+
* slug-segment pattern so a Space path and an object ref agree on what a
|
|
171
|
+
* legal segment is: starts alphanumeric, then alphanumerics plus `_`, `.`,
|
|
172
|
+
* `-`. No `/`, no whitespace, no `@` — those are grammar separators and
|
|
173
|
+
* would make the ref ambiguous.
|
|
174
|
+
*/
|
|
175
|
+
export const SPACE_SEGMENT_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Fail-fast guard for a Space path segment id. Enforced by BOTH the renderer
|
|
179
|
+
* ({@link spaceRefToPath}) and the parser, which is what makes parse/format
|
|
180
|
+
* exact inverses: a ref that cannot be rendered can never be produced, and a
|
|
181
|
+
* string that cannot be parsed can never be rendered.
|
|
182
|
+
*/
|
|
183
|
+
export function assertValidSpaceSegmentId(
|
|
184
|
+
segment: keyof SpaceRef,
|
|
185
|
+
value: string,
|
|
186
|
+
raw: string,
|
|
187
|
+
): void {
|
|
188
|
+
if (!SPACE_SEGMENT_ID_PATTERN.test(value)) {
|
|
189
|
+
throw new SpaceRefParseError(
|
|
190
|
+
`Space segment "${String(segment)}" id "${value}" must match ${SPACE_SEGMENT_ID_PATTERN}.`,
|
|
191
|
+
raw,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function requireSpaceSegment(
|
|
197
|
+
ref: SpaceRef,
|
|
198
|
+
segment: SegmentKey,
|
|
199
|
+
raw: string,
|
|
200
|
+
): string {
|
|
201
|
+
const value = ref[segment];
|
|
202
|
+
if (value === undefined || value.length === 0) {
|
|
203
|
+
throw new SpaceRefParseError(
|
|
204
|
+
`SpaceKind.${ref.tier} requires "${segment}".`,
|
|
205
|
+
raw,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
assertValidSpaceSegmentId(segment, value, raw);
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Render a `SpaceRef` as its canonical XVFS path prefix. Returns a string
|
|
214
|
+
* that starts with `/`. Fails fast if a required segment is missing or its id
|
|
215
|
+
* violates {@link SPACE_SEGMENT_ID_PATTERN}.
|
|
216
|
+
*
|
|
217
|
+
* This is the SINGLE canonical Space grammar — the plural-head form shared by
|
|
218
|
+
* `XemaObjectRef`, search refs, the object-registry projection and (since the
|
|
219
|
+
* X9 convergence) `formatSpaceRef`/`parseSpaceRef`:
|
|
220
|
+
*
|
|
221
|
+
* ```
|
|
222
|
+
* /system
|
|
223
|
+
* /users/<userId>
|
|
224
|
+
* /biomes/<biomeId>
|
|
225
|
+
* /sessions/<sessionId>
|
|
226
|
+
* /orgs/<orgId>
|
|
227
|
+
* /orgs/<orgId>/projects/<projectId>
|
|
228
|
+
* /orgs/<orgId>/projects/<projectId>/apps/<appId>
|
|
229
|
+
* ```
|
|
230
|
+
*
|
|
231
|
+
* `User`, `Biome` and `Session` are ROOT-addressable on purpose — that is
|
|
232
|
+
* exactly what {@link REQUIRED_SEGMENTS} already declares (they require only
|
|
233
|
+
* their own id), and it is what every real producer emits: a biome-contributed
|
|
234
|
+
* skill lives at `xema://biomes/<id>/skill/<slug>` and is org-less. A ref's
|
|
235
|
+
* ancestor segments are NOT part of its identity for those tiers.
|
|
154
236
|
*/
|
|
155
237
|
export function spaceRefToPath(ref: SpaceRef): string {
|
|
238
|
+
const raw = ref.path ?? `<SpaceKind.${ref.tier}>`;
|
|
156
239
|
switch (ref.tier) {
|
|
157
240
|
case SpaceKind.System:
|
|
158
241
|
return '/system';
|
|
159
242
|
case SpaceKind.Biome:
|
|
160
|
-
|
|
161
|
-
throw new Error('spaceRefToPath: SpaceKind.Biome requires biomeId');
|
|
162
|
-
}
|
|
163
|
-
return `/biomes/${ref.biomeId}`;
|
|
164
|
-
case SpaceKind.Org:
|
|
165
|
-
if (!ref.orgId) {
|
|
166
|
-
throw new Error('spaceRefToPath: SpaceKind.Org requires orgId');
|
|
167
|
-
}
|
|
168
|
-
return `/orgs/${ref.orgId}`;
|
|
169
|
-
case SpaceKind.Project:
|
|
170
|
-
if (!ref.orgId || !ref.projectId) {
|
|
171
|
-
throw new Error(
|
|
172
|
-
'spaceRefToPath: SpaceKind.Project requires orgId and projectId',
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
return `/orgs/${ref.orgId}/projects/${ref.projectId}`;
|
|
176
|
-
case SpaceKind.App:
|
|
177
|
-
if (!ref.orgId || !ref.projectId || !ref.appId) {
|
|
178
|
-
throw new Error(
|
|
179
|
-
'spaceRefToPath: SpaceKind.App requires orgId, projectId, and appId',
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
return `/orgs/${ref.orgId}/projects/${ref.projectId}/apps/${ref.appId}`;
|
|
183
|
-
case SpaceKind.Session:
|
|
184
|
-
if (!ref.sessionId) {
|
|
185
|
-
throw new Error('spaceRefToPath: SpaceKind.Session requires sessionId');
|
|
186
|
-
}
|
|
187
|
-
return `/sessions/${ref.sessionId}`;
|
|
243
|
+
return `/biomes/${requireSpaceSegment(ref, 'biomeId', raw)}`;
|
|
188
244
|
case SpaceKind.User:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
245
|
+
return `/users/${requireSpaceSegment(ref, 'userId', raw)}`;
|
|
246
|
+
case SpaceKind.Session:
|
|
247
|
+
return `/sessions/${requireSpaceSegment(ref, 'sessionId', raw)}`;
|
|
248
|
+
case SpaceKind.Org:
|
|
249
|
+
return `/orgs/${requireSpaceSegment(ref, 'orgId', raw)}`;
|
|
250
|
+
case SpaceKind.Project: {
|
|
251
|
+
const orgId = requireSpaceSegment(ref, 'orgId', raw);
|
|
252
|
+
const projectId = requireSpaceSegment(ref, 'projectId', raw);
|
|
253
|
+
return `/orgs/${orgId}/projects/${projectId}`;
|
|
254
|
+
}
|
|
255
|
+
case SpaceKind.App: {
|
|
256
|
+
const orgId = requireSpaceSegment(ref, 'orgId', raw);
|
|
257
|
+
const projectId = requireSpaceSegment(ref, 'projectId', raw);
|
|
258
|
+
const appId = requireSpaceSegment(ref, 'appId', raw);
|
|
259
|
+
return `/orgs/${orgId}/projects/${projectId}/apps/${appId}`;
|
|
260
|
+
}
|
|
261
|
+
default: {
|
|
262
|
+
const exhaustive: never = ref.tier;
|
|
263
|
+
throw new SpaceRefParseError(
|
|
264
|
+
`spaceRefToPath: unhandled SpaceKind "${String(exhaustive)}".`,
|
|
265
|
+
raw,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
193
268
|
}
|
|
194
269
|
}
|
|
195
270
|
|
|
@@ -45,11 +45,27 @@ export const SubjectKindSchema = z.nativeEnum(SubjectKind);
|
|
|
45
45
|
export interface ActingForRef {
|
|
46
46
|
kind: SubjectKind;
|
|
47
47
|
id: string;
|
|
48
|
+
/**
|
|
49
|
+
* The ISSUER-STAMPED OAuth client identity of this hop (`azp`, else
|
|
50
|
+
* `client_id`), when the issuer stamps it inside the hop's `act` object.
|
|
51
|
+
*
|
|
52
|
+
* `id` is the hop's `sub` — for a service-account token that is an opaque
|
|
53
|
+
* Keycloak UUID, which says nothing about WHICH service conveyed the call.
|
|
54
|
+
* The client id is the logical service name (`workflow-runtime-worker`,
|
|
55
|
+
* `agent-session-api`, …) that grants and audit trails are keyed by. It is
|
|
56
|
+
* never caller-supplied: an OAuth client cannot assert another client's id
|
|
57
|
+
* in a token the issuer signed.
|
|
58
|
+
*
|
|
59
|
+
* Absent when the issuer stamps neither claim on the hop — treat absence as
|
|
60
|
+
* "unknown", never as a match.
|
|
61
|
+
*/
|
|
62
|
+
clientId?: string;
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
export const ActingForRefSchema = z.object({
|
|
51
66
|
kind: SubjectKindSchema,
|
|
52
67
|
id: z.string().min(1),
|
|
68
|
+
clientId: z.string().min(1).optional(),
|
|
53
69
|
}) as z.ZodType<ActingForRef>;
|
|
54
70
|
|
|
55
71
|
/**
|