stylegallery 0.1.0 → 0.1.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.
Files changed (230) hide show
  1. package/AGENTS.md +4 -1
  2. package/DOMAINS.md +2 -6
  3. package/GOVERNANCE.md +10 -9
  4. package/README.md +23 -127
  5. package/consumer-reference/agent-native/v2/admission-policy.json +646 -0
  6. package/consumer-reference/agent-native/v2/material-registry.json +1 -0
  7. package/consumer-reference/agent-native/v2/schema/admission-policy.schema.json +50 -0
  8. package/consumer-reference/agent-native/v2/schema/material-manifest.schema.json +17 -0
  9. package/consumer-reference/agent-native/v2/schema/material-record.schema.json +20 -0
  10. package/consumer-reference/contract.md +1 -1
  11. package/consumer-reference/index.md +5 -3
  12. package/layout/index.md +124 -0
  13. package/log.md +51 -0
  14. package/package.json +181 -22
  15. package/scripts/agent-native/v2/experimental-extension-registry.mjs +143 -0
  16. package/scripts/agent-native/v2/extensions/a2a-projection.mjs +264 -0
  17. package/scripts/agent-native/v2/extensions/agui-projection.mjs +191 -0
  18. package/scripts/agent-native/v2/material-admission.mjs +295 -0
  19. package/scripts/agent-native/v2/material-cli-adapter.mjs +134 -0
  20. package/scripts/agent-native/v2/material-context.mjs +251 -0
  21. package/scripts/agent-native/v2/material-identity.mjs +71 -0
  22. package/scripts/agent-native/v2/material-mcp-adapter.mjs +253 -0
  23. package/scripts/agent-native/v2/material-operation-registry.mjs +123 -0
  24. package/scripts/agent-native/v2/material-queries.mjs +398 -0
  25. package/scripts/agent-native/v2/material-registry.mjs +319 -0
  26. package/scripts/sg-material-mcp.mjs +98 -0
  27. package/scripts/sg-material.mjs +27 -0
  28. package/consumer-reference/adapters/style-dictionary.config.mjs +0 -36
  29. package/consumer-reference/baselines/calibration.json +0 -260
  30. package/consumer-reference/baselines/manifest.json +0 -21
  31. package/consumer-reference/fixtures/consumer-conformance/e2e-fixture.mjs +0 -282
  32. package/consumer-reference/fixtures/consumer-conformance/manifest.json +0 -102
  33. package/consumer-reference/fixtures/consumer-conformance/runtime-proof.mjs +0 -32
  34. package/consumer-reference/fixtures/consumer-conformance/valid-migration-result.json +0 -18
  35. package/consumer-reference/fixtures/consumer-conformance/valid-migration.json +0 -142
  36. package/consumer-reference/fixtures/invalid-stable-ended-support.json +0 -19
  37. package/consumer-reference/fixtures/promotion/invalid-blocking-evidence.json +0 -26
  38. package/consumer-reference/fixtures/promotion/invalid-count-only.json +0 -29
  39. package/consumer-reference/fixtures/promotion/invalid-missing-owner-migration.json +0 -25
  40. package/consumer-reference/fixtures/promotion/invalid-normative-bypass-no-regression.json +0 -25
  41. package/consumer-reference/fixtures/promotion/invalid-related-counted-independent.json +0 -45
  42. package/consumer-reference/fixtures/promotion/invalid-stable-ended-support.json +0 -26
  43. package/consumer-reference/fixtures/promotion/manifest.json +0 -15
  44. package/consumer-reference/fixtures/promotion/valid-deferred-example.json +0 -67
  45. package/consumer-reference/fixtures/promotion/valid-normative-bypass.json +0 -67
  46. package/consumer-reference/fixtures/token-portability/invalid-cycle.json +0 -7
  47. package/consumer-reference/fixtures/token-portability/invalid-dangling-alias.json +0 -6
  48. package/consumer-reference/fixtures/token-portability/invalid-extends.json +0 -4
  49. package/consumer-reference/fixtures/token-portability/invalid-json-pointer.json +0 -7
  50. package/consumer-reference/fixtures/token-portability/invalid-resolver.json +0 -9
  51. package/consumer-reference/fixtures/token-portability/invalid-type-mismatch.json +0 -10
  52. package/consumer-reference/fixtures/token-portability/invalid-unknown-reserved.json +0 -6
  53. package/consumer-reference/fixtures/token-portability/invalid-untested-type.json +0 -6
  54. package/consumer-reference/fixtures/token-portability/valid-border.json +0 -14
  55. package/consumer-reference/fixtures/token-portability/valid-reference.json +0 -32
  56. package/consumer-reference/fixtures/token-portability/valid-whole-alias.json +0 -7
  57. package/consumer-reference/fixtures/valid-deprecated.json +0 -21
  58. package/consumer-reference/fixtures/valid-experimental.json +0 -19
  59. package/consumer-reference/generated/manifest.json +0 -40
  60. package/consumer-reference/generated/tokens.css +0 -12
  61. package/consumer-reference/policies/shared-experimental.json +0 -37
  62. package/consumer-reference/schema/ax-evidence.schema.json +0 -30
  63. package/consumer-reference/schema/baseline-manifest.schema.json +0 -55
  64. package/consumer-reference/schema/calibration-record.schema.json +0 -190
  65. package/consumer-reference/schema/capture-session.schema.json +0 -161
  66. package/consumer-reference/schema/component-state.schema.json +0 -150
  67. package/consumer-reference/schema/consumer-conformance-record.schema.json +0 -365
  68. package/consumer-reference/schema/dom-evidence.schema.json +0 -38
  69. package/consumer-reference/schema/evidence-record.schema.json +0 -81
  70. package/consumer-reference/schema/fixture-manifest.schema.json +0 -32
  71. package/consumer-reference/schema/item.schema.json +0 -188
  72. package/consumer-reference/schema/page-evidence-manifest.schema.json +0 -216
  73. package/consumer-reference/schema/page-evidence-session.schema.json +0 -119
  74. package/consumer-reference/schema/portable-tokens.schema.json +0 -104
  75. package/consumer-reference/schema/promotion-rfc.schema.json +0 -219
  76. package/consumer-reference/schema/runtime-evidence-manifest.schema.json +0 -33
  77. package/consumer-reference/schema/visual-evidence.schema.json +0 -31
  78. package/design-engineering/consumer-migration-readiness.md +0 -124
  79. package/design-engineering/interface-craft.md +0 -107
  80. package/design-engineering/reference-profiles/external-adaptation/index.md +0 -12
  81. package/design-engineering/reference-profiles/governed-local/editorial/components/button.component.json +0 -125
  82. package/design-engineering/reference-profiles/governed-local/editorial/evidence/button.evidence.json +0 -3504
  83. package/design-engineering/reference-profiles/governed-local/editorial/fixtures/button.fixture.json +0 -65
  84. package/design-engineering/reference-profiles/governed-local/editorial/generated/evidence-coverage.md +0 -23
  85. package/design-engineering/reference-profiles/governed-local/editorial/generated/keyboard-matrix.md +0 -19
  86. package/design-engineering/reference-profiles/governed-local/editorial/generated/state-matrix.md +0 -21
  87. package/design-engineering/reference-profiles/governed-local/editorial/local-foundations.json +0 -12
  88. package/design-engineering/reference-profiles/governed-local/editorial/states/button.states.json +0 -195
  89. package/design-engineering/reference-profiles/governed-local/editorial/tokens.dtcg.json +0 -17
  90. package/design-engineering/reference-profiles/governed-local/terminal/components/button.component.json +0 -125
  91. package/design-engineering/reference-profiles/governed-local/terminal/evidence/button.evidence.json +0 -3504
  92. package/design-engineering/reference-profiles/governed-local/terminal/fixtures/button.fixture.json +0 -65
  93. package/design-engineering/reference-profiles/governed-local/terminal/generated/evidence-coverage.md +0 -23
  94. package/design-engineering/reference-profiles/governed-local/terminal/generated/keyboard-matrix.md +0 -19
  95. package/design-engineering/reference-profiles/governed-local/terminal/generated/state-matrix.md +0 -21
  96. package/design-engineering/reference-profiles/governed-local/terminal/local-foundations.json +0 -12
  97. package/design-engineering/reference-profiles/governed-local/terminal/profile.json +0 -43
  98. package/design-engineering/reference-profiles/governed-local/terminal/states/button.states.json +0 -195
  99. package/design-engineering/reference-profiles/governed-local/terminal/tokens.dtcg.json +0 -17
  100. package/game-ui/classification.md +0 -82
  101. package/game-ui/reference-record.md +0 -62
  102. package/game-ui/screen-hierarchy.md +0 -107
  103. package/game-ui/unity/architecture.md +0 -166
  104. package/game-ui/unity/cli-loop.md +0 -84
  105. package/game-ui/unity/data/unity-technologies-public-repositories.json +0 -26654
  106. package/game-ui/unity/org-wiki.md +0 -146
  107. package/game-ui/unity/repository-map.md +0 -102
  108. package/game-ui/unity/ui-systems.md +0 -85
  109. package/motion/practice-reference.md +0 -145
  110. package/motion/review-workflow.md +0 -86
  111. package/motion/vocabulary.md +0 -82
  112. package/platform-guides/apple-interaction.md +0 -89
  113. package/scripts/agent-native/events.mjs +0 -113
  114. package/scripts/agent-native/immutable-store.mjs +0 -102
  115. package/scripts/artifact-metadata.mjs +0 -20
  116. package/scripts/audit-evidence-freshness.mjs +0 -308
  117. package/scripts/baseline-contract.mjs +0 -189
  118. package/scripts/baseline-schema-parity.mjs +0 -40
  119. package/scripts/build-reference-artifacts.mjs +0 -127
  120. package/scripts/calibration-raw-contract.mjs +0 -172
  121. package/scripts/capture-session-contract.mjs +0 -187
  122. package/scripts/component-state-artifact-fixture.mjs +0 -44
  123. package/scripts/component-state-contract.mjs +0 -99
  124. package/scripts/component-state-semantics.mjs +0 -128
  125. package/scripts/component-state-workflow-contract.mjs +0 -67
  126. package/scripts/component-state-workflow-negative-cases.mjs +0 -64
  127. package/scripts/consumer-conformance-contract.mjs +0 -198
  128. package/scripts/consumer-evidence-governance-contract.mjs +0 -210
  129. package/scripts/consumer-reference-case-runner.mjs +0 -93
  130. package/scripts/consumer-reference-schema.mjs +0 -155
  131. package/scripts/create-component-state-session.mjs +0 -100
  132. package/scripts/create-page-evidence-session.mjs +0 -139
  133. package/scripts/domain-document-boundaries.mjs +0 -40
  134. package/scripts/domain-validation-checks.mjs +0 -141
  135. package/scripts/evidence-artifact-contract.mjs +0 -191
  136. package/scripts/finalize-component-state-evidence.mjs +0 -165
  137. package/scripts/finalize-page-evidence.mjs +0 -197
  138. package/scripts/generate-consumer-reference-evidence.mjs +0 -154
  139. package/scripts/generate-patterns.mjs +0 -218
  140. package/scripts/governance-matrix-contract.mjs +0 -73
  141. package/scripts/governance-matrix-negative-cases.mjs +0 -114
  142. package/scripts/governance-policy-contract.mjs +0 -49
  143. package/scripts/governance-test-fixture.mjs +0 -321
  144. package/scripts/governance-workflow-negative-cases.mjs +0 -241
  145. package/scripts/governed-profile-registry.mjs +0 -125
  146. package/scripts/json-schema-formats.mjs +0 -31
  147. package/scripts/markdown-structure.mjs +0 -45
  148. package/scripts/page-artifact-metadata.mjs +0 -124
  149. package/scripts/page-evidence-contract.mjs +0 -297
  150. package/scripts/page-evidence-fixture.mjs +0 -271
  151. package/scripts/pattern-data.mjs +0 -325
  152. package/scripts/profile-record-contract.mjs +0 -140
  153. package/scripts/promotion-attestation-contract.mjs +0 -91
  154. package/scripts/promotion-boundary-test-contract.mjs +0 -92
  155. package/scripts/promotion-fixture-inventory.mjs +0 -109
  156. package/scripts/promotion-governance-contract.mjs +0 -28
  157. package/scripts/reference-artifact-path-contract.mjs +0 -28
  158. package/scripts/reference-profile-contract.mjs +0 -114
  159. package/scripts/reference-token-contract.mjs +0 -156
  160. package/scripts/run-consumer-page-evidence-ci.mjs +0 -170
  161. package/scripts/summarize-sentinel-calibration.mjs +0 -98
  162. package/scripts/test-agent-adapter-conformance.mjs +0 -250
  163. package/scripts/test-agent-conformance-receipt.mjs +0 -106
  164. package/scripts/test-agent-events.mjs +0 -81
  165. package/scripts/test-agent-execution-effects.mjs +0 -114
  166. package/scripts/test-agent-execution-fixtures.mjs +0 -140
  167. package/scripts/test-agent-execution-kernel.mjs +0 -39
  168. package/scripts/test-agent-execution-learning.mjs +0 -130
  169. package/scripts/test-agent-execution-lifecycle.mjs +0 -143
  170. package/scripts/test-agent-execution-safety.mjs +0 -158
  171. package/scripts/test-agent-execution-views.mjs +0 -117
  172. package/scripts/test-agent-identity-integrity-cases.mjs +0 -160
  173. package/scripts/test-agent-knowledge-kernel.mjs +0 -183
  174. package/scripts/test-agent-learning.mjs +0 -246
  175. package/scripts/test-agent-mcp.mjs +0 -246
  176. package/scripts/test-agent-native-schemas.mjs +0 -215
  177. package/scripts/test-agent-projections.mjs +0 -249
  178. package/scripts/test-agent-registry-mcp-integrity.mjs +0 -215
  179. package/scripts/test-agent-registry-mutations.mjs +0 -110
  180. package/scripts/test-audit-evidence-freshness.mjs +0 -177
  181. package/scripts/test-component-state-sentinel.mjs +0 -35
  182. package/scripts/test-component-state-source-contract.mjs +0 -83
  183. package/scripts/test-consumer-conformance-sentinel.mjs +0 -165
  184. package/scripts/test-consumer-reference-sentinel.mjs +0 -31
  185. package/scripts/test-generate-consumer-reference-evidence.mjs +0 -124
  186. package/scripts/test-json-cli-output.mjs +0 -151
  187. package/scripts/test-json-schema-formats.mjs +0 -40
  188. package/scripts/test-reference-adapters.mjs +0 -199
  189. package/scripts/test-renderer-purity.mjs +0 -34
  190. package/scripts/test-sg-cli.mjs +0 -129
  191. package/scripts/test-summarize-sentinel-calibration.mjs +0 -226
  192. package/scripts/test-validate-baseline-manifest.mjs +0 -234
  193. package/scripts/test-validate-component-state-artifacts.mjs +0 -243
  194. package/scripts/test-validate-component-state.mjs +0 -106
  195. package/scripts/test-validate-consumer-conformance.mjs +0 -427
  196. package/scripts/test-validate-consumer-reference-cli.mjs +0 -69
  197. package/scripts/test-validate-consumer-reference.mjs +0 -263
  198. package/scripts/test-validate-domains.mjs +0 -357
  199. package/scripts/test-validate-governance.mjs +0 -292
  200. package/scripts/test-validate-ia.mjs +0 -262
  201. package/scripts/test-validate-okf.mjs +0 -73
  202. package/scripts/test-validate-page-evidence.mjs +0 -302
  203. package/scripts/test-validate-patterns.mjs +0 -157
  204. package/scripts/test-validate-promotion-rfc.mjs +0 -250
  205. package/scripts/test-validate-reference-profiles.mjs +0 -170
  206. package/scripts/test-validate-unity-org-wiki.mjs +0 -57
  207. package/scripts/test-validate-unity-source-contracts.mjs +0 -67
  208. package/scripts/test-validate-vocabulary.mjs +0 -174
  209. package/scripts/test-validate-webpage-workflow.mjs +0 -293
  210. package/scripts/validate-baseline-manifest.mjs +0 -44
  211. package/scripts/validate-catalog.mjs +0 -65
  212. package/scripts/validate-component-state.mjs +0 -141
  213. package/scripts/validate-consumer-conformance.mjs +0 -477
  214. package/scripts/validate-consumer-reference.mjs +0 -288
  215. package/scripts/validate-domains.mjs +0 -237
  216. package/scripts/validate-governance.mjs +0 -248
  217. package/scripts/validate-ia.mjs +0 -136
  218. package/scripts/validate-links.mjs +0 -66
  219. package/scripts/validate-okf.mjs +0 -104
  220. package/scripts/validate-page-evidence.mjs +0 -191
  221. package/scripts/validate-patterns.mjs +0 -246
  222. package/scripts/validate-promotion-rfc.mjs +0 -224
  223. package/scripts/validate-reference-artifacts.mjs +0 -125
  224. package/scripts/validate-renderer-purity.mjs +0 -56
  225. package/scripts/validate-unity-org-wiki.mjs +0 -146
  226. package/scripts/validate-unity-source-contracts.mjs +0 -107
  227. package/scripts/validate-vocabulary.mjs +0 -115
  228. package/scripts/validate-webpage-workflow.mjs +0 -254
  229. package/scripts/visual-expectation-contract.mjs +0 -20
  230. package/scripts/workflow-action-contract.mjs +0 -163
@@ -0,0 +1,251 @@
1
+ import { hashCanonical, canonicalize, deepFreeze } from "../canonical-json.mjs";
2
+ import { materialAdmissionPolicy } from "./material-admission.mjs";
3
+ import {
4
+ captureMaterialQuerySnapshot,
5
+ MATERIAL_GET_MAX_BYTES,
6
+ materialGet,
7
+ materialSearch,
8
+ rethrowMaterialQuerySnapshotError,
9
+ verifyMaterialQuerySnapshot,
10
+ } from "./material-queries.mjs";
11
+
12
+ export const MATERIAL_CONTEXT_DEFAULT_TOKENS = 8192;
13
+ export const MATERIAL_CONTEXT_MIN_TOKENS = 256;
14
+ export const MATERIAL_CONTEXT_MAX_TOKENS = 32768;
15
+ export const MATERIAL_CONTEXT_OPERATION_VERSION = "material-context@1";
16
+ export const MATERIAL_CONTEXT_TOKEN_CONTRACT = "ceil(canonical UTF-8 response bytes / 4)";
17
+ export const MATERIAL_CONTEXT_CACHE_CONTRACT = "material-context-cache@1";
18
+
19
+ const TRUST_INPUTS = new Set([
20
+ "claim", "claims", "evidence", "evidence_links", "governance", "governance_decisions",
21
+ "merge", "merge_flags", "merge_trust", "policy_dispositions", "trust_records", "validations",
22
+ ]);
23
+ const PATH_INPUTS = new Set(["path", "repository_path", "repository_paths", "repository_root"]);
24
+ const MANIFEST_INPUTS = new Set(["manifest", "manifest_head", "manifest_override", "manifest_version_id"]);
25
+
26
+ export class MaterialContextError extends TypeError {
27
+ constructor(code, message) {
28
+ super(message);
29
+ this.name = "MaterialContextError";
30
+ this.code = code;
31
+ }
32
+ }
33
+
34
+ function fail(code, message) { throw new MaterialContextError(code, message); }
35
+ function unsafeInput() { fail("material_context_input_unsafe", "material context input must be plain JSON data"); }
36
+ function snapshotOwnData(value, seen = new WeakSet()) {
37
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
38
+ if (typeof value === "number") {
39
+ if (!Number.isFinite(value)) unsafeInput();
40
+ return value;
41
+ }
42
+ if (!value || typeof value !== "object" || seen.has(value)) unsafeInput();
43
+ const array = Array.isArray(value);
44
+ const prototype = Object.getPrototypeOf(value);
45
+ if (prototype !== (array ? Array.prototype : Object.prototype) && prototype !== null) unsafeInput();
46
+ const keys = Reflect.ownKeys(value);
47
+ if (keys.some((key) => typeof key !== "string" || key === "__proto__" || key === "prototype" || key === "constructor")) unsafeInput();
48
+ const descriptors = Object.getOwnPropertyDescriptors(value);
49
+ if (keys.some((key) => !Object.hasOwn(descriptors[key], "value") || (descriptors[key].enumerable !== true && !(array && key === "length")))) unsafeInput();
50
+ if (array && (keys.some((key) => key !== "length" && (!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= value.length))
51
+ || descriptors.length?.enumerable !== false)) unsafeInput();
52
+ seen.add(value);
53
+ const clone = array ? [] : Object.create(null);
54
+ for (const key of keys) {
55
+ if (array && key === "length") continue;
56
+ clone[key] = snapshotOwnData(descriptors[key].value, seen);
57
+ }
58
+ seen.delete(value);
59
+ return clone;
60
+ }
61
+ function assertInput(input) {
62
+ if (!input || typeof input !== "object" || Array.isArray(input)) fail("material_input_invalid", "input must be an object");
63
+ const snapshot = snapshotOwnData(input);
64
+ const keys = Object.keys(snapshot);
65
+ if (keys.some((key) => TRUST_INPUTS.has(key) || /^(claim|evidence|governance|policy|trust|validation)(?:_|$)/.test(key) || key.includes("merge"))) {
66
+ fail("material_context_trust_merge_forbidden", "trust records cannot be merged into material retrieval");
67
+ }
68
+ if (keys.some((key) => PATH_INPUTS.has(key) || /^(repository|source)_paths?$/.test(key))) {
69
+ fail("material_context_repository_path_forbidden", "repository paths are not caller inputs");
70
+ }
71
+ if (keys.some((key) => MANIFEST_INPUTS.has(key) || key.startsWith("manifest_"))) {
72
+ fail("material_context_manifest_override_forbidden", "the material manifest cannot be overridden");
73
+ }
74
+ if (keys.some((key) => key !== "query" && key !== "budget_tokens")) fail("material_input_unknown", "input contains an unsupported field");
75
+ const budgetTokens = snapshot.budget_tokens ?? MATERIAL_CONTEXT_DEFAULT_TOKENS;
76
+ if (!Number.isInteger(budgetTokens) || budgetTokens < MATERIAL_CONTEXT_MIN_TOKENS || budgetTokens > MATERIAL_CONTEXT_MAX_TOKENS) {
77
+ fail("material_context_budget_invalid", "budget_tokens must be an integer from 256 to 32768");
78
+ }
79
+ return { budgetTokens, query: snapshot.query };
80
+ }
81
+
82
+ function withoutCacheKey(context) {
83
+ const value = structuredClone(context);
84
+ delete value.cache_key;
85
+ return value;
86
+ }
87
+
88
+ /** Recompute the canonical cache key. The key binds every output field except itself. */
89
+ export function materialContextCacheKey(context) {
90
+ return hashCanonical({ contract: MATERIAL_CONTEXT_CACHE_CONTRACT, output: withoutCacheKey(context) });
91
+ }
92
+
93
+ /** Count the complete successful operation response, not only excerpt bodies. */
94
+ export function countMaterialContextTokens(context) {
95
+ const response = { ok: true, operation: "material-context", result: context };
96
+ return Math.ceil(Buffer.byteLength(canonicalize(response), "utf8") / 4);
97
+ }
98
+
99
+ function finalizeContext(value) {
100
+ const context = structuredClone(value);
101
+ context.cache_key = "sha256:" + "0".repeat(64);
102
+ for (let attempt = 0; attempt < 8; attempt += 1) {
103
+ context.cache_key = materialContextCacheKey(context);
104
+ const count = countMaterialContextTokens(context);
105
+ if (context.budget.used_tokens === count) {
106
+ context.cache_key = materialContextCacheKey(context);
107
+ return deepFreeze(context);
108
+ }
109
+ context.budget.used_tokens = count;
110
+ }
111
+ fail("material_context_accounting_failed", "material context accounting did not converge");
112
+ }
113
+
114
+ function contextValue({ normalizedQuery, tokens, manifestVersionId, totalMatches, budgetTokens, excerpts }) {
115
+ const truncated = excerpts.length < totalMatches || excerpts.some((excerpt) => excerpt.truncated);
116
+ return finalizeContext({
117
+ schema_version: "2.0",
118
+ record_kind: "material_context",
119
+ operation_version: MATERIAL_CONTEXT_OPERATION_VERSION,
120
+ normalized_query: normalizedQuery,
121
+ query_tokens: tokens,
122
+ heads: {
123
+ manifest_ref: "sg:manifest/material-admission-v2",
124
+ manifest_version_id: manifestVersionId,
125
+ policy_ref: materialAdmissionPolicy.stable_ref,
126
+ policy_version_id: materialAdmissionPolicy.version_id,
127
+ },
128
+ member_manifest: excerpts.map(({ source }) => ({
129
+ stable_ref: source.stable_ref,
130
+ version_id: source.version_id,
131
+ sha256: source.sha256,
132
+ })),
133
+ budget: {
134
+ limit_tokens: budgetTokens,
135
+ used_tokens: 0,
136
+ token_count_contract: MATERIAL_CONTEXT_TOKEN_CONTRACT,
137
+ },
138
+ untrusted_retrieval: {
139
+ classification: "untrusted_material_excerpts",
140
+ retrieval_only: true,
141
+ total_matches: totalMatches,
142
+ truncated,
143
+ excerpts,
144
+ },
145
+ });
146
+ }
147
+
148
+ function excerptOf(page, content) {
149
+ const bytes = Buffer.from(content, "utf8");
150
+ return {
151
+ material_ref: page.identity.stable_ref,
152
+ source: {
153
+ stable_ref: page.source.stable_ref,
154
+ version_id: page.source.version_id,
155
+ sha256: page.source.source_sha256,
156
+ byte_offset: page.offset,
157
+ byte_length: bytes.byteLength,
158
+ },
159
+ truncated: page.offset + bytes.byteLength < page.source.byte_length,
160
+ content,
161
+ };
162
+ }
163
+
164
+ function codePointPrefixes(content) {
165
+ const prefixes = [""];
166
+ let current = "";
167
+ for (const point of content) {
168
+ current += point;
169
+ prefixes.push(current);
170
+ }
171
+ return prefixes;
172
+ }
173
+
174
+ /** Build a closed, read-only v2 material retrieval package. No source body cache is retained. */
175
+ export function materialContext({ repositoryRoot, input, fileSystem, sourceReader, gitRunner }) {
176
+ const { budgetTokens, query } = assertInput(input);
177
+ const transaction = captureMaterialQuerySnapshot({ repositoryRoot, fileSystem, gitRunner });
178
+ let search;
179
+ try {
180
+ search = materialSearch({
181
+ repositoryRoot,
182
+ input: { query, limit: 100 },
183
+ fileSystem,
184
+ sourceReader,
185
+ gitRunner,
186
+ snapshot: transaction,
187
+ });
188
+ } catch (error) {
189
+ rethrowMaterialQuerySnapshotError({ snapshot: transaction, error, fileSystem });
190
+ }
191
+ const normalizedQuery = search.scoring_tokens.join(" ");
192
+ const pages = [];
193
+ let excerpts = [];
194
+
195
+ // Membership is a maximal rank-ordered prefix at one code point per source.
196
+ // It is therefore deterministic and monotonic as the caller budget grows.
197
+ for (const result of search.results) {
198
+ const current = contextValue({ normalizedQuery, tokens: search.scoring_tokens, manifestVersionId: search.manifest_version_id, totalMatches: search.total_matches, budgetTokens, excerpts });
199
+ const remainingBytes = Math.max(4, (budgetTokens - current.budget.used_tokens) * 4);
200
+ let page;
201
+ try {
202
+ page = materialGet({
203
+ repositoryRoot,
204
+ input: { reference: result.source.stable_ref, offset: 0, length: Math.min(result.source.byte_length, MATERIAL_GET_MAX_BYTES, remainingBytes) },
205
+ fileSystem,
206
+ sourceReader,
207
+ gitRunner,
208
+ snapshot: transaction,
209
+ contextUtf8SafeEnd: true,
210
+ });
211
+ } catch (error) {
212
+ rethrowMaterialQuerySnapshotError({ snapshot: transaction, error, fileSystem });
213
+ }
214
+ const prefixes = codePointPrefixes(page.content);
215
+ const minimum = prefixes[Math.min(1, prefixes.length - 1)];
216
+ const candidate = [...excerpts, excerptOf(page, minimum)];
217
+ const packaged = contextValue({ normalizedQuery, tokens: search.scoring_tokens, manifestVersionId: search.manifest_version_id, totalMatches: search.total_matches, budgetTokens, excerpts: candidate });
218
+ if (packaged.budget.used_tokens > budgetTokens) break;
219
+ excerpts = candidate;
220
+ pages.push({ result, page, prefixes });
221
+ }
222
+
223
+ // Spend remaining budget in rank order without changing membership.
224
+ for (let index = 0; index < pages.length; index += 1) {
225
+ const { page, prefixes } = pages[index];
226
+ let low = 1;
227
+ let high = prefixes.length - 1;
228
+ let best = excerpts[index].content;
229
+ while (low <= high) {
230
+ const middle = Math.floor((low + high) / 2);
231
+ const candidateExcerpts = excerpts.slice();
232
+ candidateExcerpts[index] = excerptOf(page, prefixes[middle]);
233
+ const packaged = contextValue({ normalizedQuery, tokens: search.scoring_tokens, manifestVersionId: search.manifest_version_id, totalMatches: search.total_matches, budgetTokens, excerpts: candidateExcerpts });
234
+ if (packaged.budget.used_tokens <= budgetTokens) { best = prefixes[middle]; low = middle + 1; }
235
+ else high = middle - 1;
236
+ }
237
+ excerpts[index] = excerptOf(page, best);
238
+ }
239
+
240
+ const context = contextValue({ normalizedQuery, tokens: search.scoring_tokens, manifestVersionId: search.manifest_version_id, totalMatches: search.total_matches, budgetTokens, excerpts });
241
+ if (context.budget.used_tokens > budgetTokens) fail("material_context_budget_too_small", "budget cannot contain the material context envelope");
242
+ verifyMaterialQuerySnapshot({
243
+ repositoryRoot,
244
+ snapshot: transaction,
245
+ selectedSourceRefs: excerpts.map((excerpt) => excerpt.source.stable_ref),
246
+ fileSystem,
247
+ sourceReader,
248
+ gitRunner,
249
+ });
250
+ return context;
251
+ }
@@ -0,0 +1,71 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { deepFreeze } from "../canonical-json.mjs";
4
+
5
+ export const MATERIAL_STABLE_REF_KINDS = Object.freeze(["domain", "page", "pattern", "material"]);
6
+ const KIND_SET = new Set(MATERIAL_STABLE_REF_KINDS);
7
+ const KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
8
+ const PATH_ID = /^path-sha256-[a-f0-9]{64}$/;
9
+ const DOMAIN_IDS = new Set(["layout", "motion", "design-engineering", "game-ui", "platform-guides"]);
10
+ const MATERIAL_ID = PATH_ID;
11
+
12
+ export class MaterialIdentityError extends TypeError {
13
+ constructor(code, message) {
14
+ super(message);
15
+ this.name = "MaterialIdentityError";
16
+ this.code = code;
17
+ }
18
+ }
19
+
20
+ function fail(code, message) { throw new MaterialIdentityError(code, message); }
21
+ function sha256(value) { return createHash("sha256").update(value).digest("hex"); }
22
+ function referenceOf(input) {
23
+ if (typeof input === "string") return input;
24
+ if (!input || typeof input !== "object" || Array.isArray(input)) return undefined;
25
+ return input.stable_ref ?? input.reference;
26
+ }
27
+
28
+ /** Parse only the closed v2 material identity grammar. This is deliberately not part of v1 identity.mjs. */
29
+ export function parseMaterialStableRef(input) {
30
+ if (input && typeof input === "object" && Object.hasOwn(input, "schema_version") && input.schema_version !== "2.0") {
31
+ fail("material_identity_version_invalid", "material identities require schema_version 2.0");
32
+ }
33
+ const reference = referenceOf(input);
34
+ if (typeof reference !== "string") fail("material_stable_ref_required", "v2 StableRef must be a string");
35
+ const match = /^sg:(domain|page|pattern|material)\/([a-z0-9]+(?:-[a-z0-9]+)*)$/.exec(reference);
36
+ if (!match || !KIND_SET.has(match[1])) fail("material_stable_ref_invalid", "reference is not a v2 material StableRef");
37
+ const [, kind, id] = match;
38
+ if ((kind === "domain" && !DOMAIN_IDS.has(id))
39
+ || ((kind === "page" || kind === "pattern" || kind === "material") && !PATH_ID.test(id))) {
40
+ fail("material_stable_ref_invalid", "reference is outside the closed v2 material identity grammar");
41
+ }
42
+ return deepFreeze({ schema_version: "2.0", stable_ref: reference, kind, id });
43
+ }
44
+
45
+ export function createMaterialStableRef({ kind, id }) {
46
+ if (!KIND_SET.has(kind)) fail("material_stable_ref_kind_invalid", "unsupported v2 material StableRef kind");
47
+ if (typeof id !== "string" || !KEBAB.test(id)) fail("material_stable_ref_id_invalid", "v2 material StableRef id must be kebab-case");
48
+ return parseMaterialStableRef(`sg:${kind}/${id}`).stable_ref;
49
+ }
50
+
51
+ export function materialIdentityForRecord(record) {
52
+ if (!record || typeof record !== "object" || !MATERIAL_ID.test(record.stable_ref?.replace(/^sg:material\//, "") ?? "")) {
53
+ fail("material_record_identity_invalid", "record does not carry a v2 material source identity");
54
+ }
55
+ const repositoryPath = record.repository_path;
56
+ if (typeof repositoryPath !== "string") fail("material_record_identity_invalid", "record has no bound source path");
57
+ let kind = "page";
58
+ let id = `path-sha256-${sha256(Buffer.from(repositoryPath, "utf8"))}`;
59
+ const domainMatch = /^(layout|motion|design-engineering|game-ui|platform-guides)\/index\.md$/.exec(repositoryPath);
60
+ if (domainMatch) { kind = "domain"; id = domainMatch[1]; }
61
+ else if (/^patterns\/[^/]+\/[^/]+\.md$/.test(repositoryPath)) kind = "pattern";
62
+ return deepFreeze({
63
+ schema_version: "2.0",
64
+ stable_ref: createMaterialStableRef({ kind, id }),
65
+ kind,
66
+ source_ref: record.stable_ref,
67
+ source_version_id: record.version_id,
68
+ });
69
+ }
70
+
71
+ export const parseV2StableRef = parseMaterialStableRef;
@@ -0,0 +1,253 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { types as utilTypes } from "node:util";
5
+
6
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { CallToolRequestSchema, ErrorCode, McpError, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
8
+ import { z } from "zod";
9
+
10
+ import { canonicalize } from "../canonical-json.mjs";
11
+ import { parseMaterialStableRef } from "./material-identity.mjs";
12
+ import { createMaterialOperationRegistry, materialOperationSpecs } from "./material-operation-registry.mjs";
13
+
14
+ const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
15
+ const manifestPath = path.join(repositoryRoot, "consumer-reference", "agent-native", "v2", "material-registry.json");
16
+ const exposedNames = new Set(["material-context", "material-discover", "material-get", "material-search"]);
17
+ const descriptions = Object.freeze({
18
+ "material-context": "Build a deterministic, provenance-bound material context within a token budget.",
19
+ "material-discover": "Discover the five governed StyleGallery domain entries.",
20
+ "material-get": "Read bounded UTF-8 bytes for one canonical v2 material StableRef.",
21
+ "material-search": "Search admitted material deterministically using normalized Unicode terms.",
22
+ });
23
+ const inputSchemas = Object.freeze({
24
+ "material-context": z.strictObject({ query: z.string(), budget_tokens: z.number().int().min(256).max(32768).default(8192) }),
25
+ "material-discover": z.strictObject({}),
26
+ "material-get": z.strictObject({ reference: z.string(), offset: z.number().int().min(0).optional(), length: z.number().int().min(1).max(65536).optional() }),
27
+ "material-search": z.strictObject({ query: z.string(), limit: z.number().int().min(1).max(100).optional() }),
28
+ });
29
+ const outputSchema = z.strictObject({
30
+ ok: z.boolean(),
31
+ operation: z.string().nullable(),
32
+ result: z.looseObject({}).optional(),
33
+ failures: z.array(z.looseObject({})).optional(),
34
+ });
35
+ const safeCallToolRequestSchema = CallToolRequestSchema.extend({
36
+ params: CallToolRequestSchema.shape.params.extend({ arguments: z.unknown().optional() }),
37
+ });
38
+ const unsafeInputMarker = Object.freeze({ __stylegallery_unsafe_material_input__: true });
39
+
40
+ function packagedInventoryRunner(root) {
41
+ return () => {
42
+ try {
43
+ const manifest = JSON.parse(fs.readFileSync(path.join(root, "consumer-reference", "agent-native", "v2", "material-registry.json"), "utf8"));
44
+ return {
45
+ error: undefined,
46
+ status: 0,
47
+ stderr: Buffer.alloc(0),
48
+ stdout: Buffer.from(manifest.materials.map((record) => `100644 ${record.source_sha256} 0\t${record.repository_path}\0`).join(""), "utf8"),
49
+ };
50
+ } catch {
51
+ return { error: undefined, status: 1, stderr: Buffer.alloc(0), stdout: Buffer.alloc(0) };
52
+ }
53
+ };
54
+ }
55
+
56
+ function runtimeRegistry() {
57
+ return createMaterialOperationRegistry({
58
+ repositoryRoot,
59
+ ...(fs.existsSync(path.join(repositoryRoot, ".git")) ? {} : { gitRunner: packagedInventoryRunner(repositoryRoot) }),
60
+ });
61
+ }
62
+
63
+ function loadManifest() {
64
+ return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
65
+ }
66
+
67
+ function assertRegistry(registry) {
68
+ if (!registry || typeof registry.invoke !== "function" || !Array.isArray(registry.operations)) {
69
+ throw new TypeError("material MCP registry must expose invoke and operations");
70
+ }
71
+ const names = registry.operations.map(({ name }) => name).sort();
72
+ if (names.length !== exposedNames.size || names.some((name) => !exposedNames.has(name))) {
73
+ throw new TypeError("material MCP registry must contain exactly four v2 read operations");
74
+ }
75
+ for (const operation of registry.operations) {
76
+ if (operation.read_only !== true || operation.effect_class !== "NONE") {
77
+ throw new TypeError("material MCP operations must be read-only with NONE effects");
78
+ }
79
+ }
80
+ }
81
+
82
+ export function normalizeMaterialMcpInput(input) {
83
+ if (input === undefined) return {};
84
+ if (input === null || typeof input !== "object" || utilTypes.isProxy(input) || Array.isArray(input)) {
85
+ throw new McpError(ErrorCode.InvalidParams, "operation input must contain only own plain data");
86
+ }
87
+ let prototype;
88
+ let keys;
89
+ try {
90
+ prototype = Object.getPrototypeOf(input);
91
+ keys = Reflect.ownKeys(input);
92
+ } catch {
93
+ throw new McpError(ErrorCode.InvalidParams, "operation input must contain only own plain data");
94
+ }
95
+ if (prototype !== Object.prototype && prototype !== null) {
96
+ throw new McpError(ErrorCode.InvalidParams, "operation input must contain only own plain data");
97
+ }
98
+ const normalized = {};
99
+ for (const key of keys) {
100
+ if (typeof key !== "string") throw new McpError(ErrorCode.InvalidParams, "operation input must contain only own plain data");
101
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
102
+ if (!descriptor || descriptor.enumerable !== true || !("value" in descriptor)) {
103
+ throw new McpError(ErrorCode.InvalidParams, "operation input must contain only own plain data");
104
+ }
105
+ const value = descriptor.value;
106
+ if (!["string", "number", "boolean"].includes(typeof value) && value !== null) {
107
+ throw new McpError(ErrorCode.InvalidParams, "operation input must contain only own scalar data");
108
+ }
109
+ normalized[key] = value;
110
+ }
111
+ return normalized;
112
+ }
113
+
114
+ function sanitizedToolCallMessage(message) {
115
+ if (!message || typeof message !== "object" || message.method !== "tools/call" || !message.params || typeof message.params !== "object") return message;
116
+ const descriptor = Object.getOwnPropertyDescriptor(message.params, "arguments");
117
+ let normalized;
118
+ try {
119
+ normalized = descriptor && "value" in descriptor ? normalizeMaterialMcpInput(descriptor.value) : {};
120
+ } catch {
121
+ normalized = unsafeInputMarker;
122
+ }
123
+ const params = Object.create(Object.getPrototypeOf(message.params), Object.getOwnPropertyDescriptors(message.params));
124
+ Object.defineProperty(params, "arguments", { configurable: true, enumerable: true, value: normalized, writable: true });
125
+ const sanitized = Object.create(Object.getPrototypeOf(message), Object.getOwnPropertyDescriptors(message));
126
+ Object.defineProperty(sanitized, "params", { configurable: true, enumerable: true, value: params, writable: true });
127
+ return sanitized;
128
+ }
129
+
130
+ class OwnDataTransport {
131
+ onclose;
132
+ onerror;
133
+ onmessage;
134
+
135
+ constructor(transport) { this.transport = transport; }
136
+ get sessionId() { return this.transport.sessionId; }
137
+ async start() {
138
+ this.transport.onmessage = (message, extra) => this.onmessage?.(sanitizedToolCallMessage(message), extra);
139
+ this.transport.onerror = (error) => this.onerror?.(error);
140
+ this.transport.onclose = () => this.onclose?.();
141
+ await this.transport.start();
142
+ }
143
+ close() { return this.transport.close(); }
144
+ send(message, options) { return this.transport.send(message, options); }
145
+ setProtocolVersion(version) { return this.transport.setProtocolVersion?.(version); }
146
+ }
147
+
148
+ function toolResponse(value) {
149
+ return {
150
+ content: [{ type: "text", text: canonicalize(value) }],
151
+ ...(value.ok ? {} : { isError: true }),
152
+ structuredContent: value,
153
+ };
154
+ }
155
+
156
+ function resourceContents(uri, value) {
157
+ return { contents: [{ uri, mimeType: "application/json", text: canonicalize(value) }] };
158
+ }
159
+
160
+ export function materialResourceUri(stableRef) {
161
+ return `sg://v2/material/${encodeURIComponent(parseMaterialStableRef(stableRef).stable_ref)}`;
162
+ }
163
+
164
+ function materialReference(encoded) {
165
+ if (typeof encoded !== "string" || encoded.length === 0 || encoded.length > 256) {
166
+ throw new McpError(ErrorCode.InvalidParams, "material resource reference must be one canonical encoded StableRef");
167
+ }
168
+ let decoded;
169
+ try { decoded = decodeURIComponent(encoded); } catch {
170
+ throw new McpError(ErrorCode.InvalidParams, "material resource reference has invalid percent encoding");
171
+ }
172
+ if (encodeURIComponent(decoded) !== encoded) {
173
+ throw new McpError(ErrorCode.InvalidParams, "material resource reference is not canonically encoded once");
174
+ }
175
+ try { return parseMaterialStableRef(decoded).stable_ref; } catch {
176
+ throw new McpError(ErrorCode.InvalidParams, "material resource reference must decode to one canonical v2 StableRef");
177
+ }
178
+ }
179
+
180
+ function resourceEnvelope(registry, encoded) {
181
+ const result = registry.invoke("material-get", { reference: materialReference(encoded) });
182
+ if (result.ok) return result;
183
+ const failure = result.failures?.[0] ?? { code: "material_operation_failed", message: "v2 material operation failed" };
184
+ throw new McpError(ErrorCode.InvalidParams, `${failure.code}: ${failure.message}`);
185
+ }
186
+
187
+ function materialReferenceFromUri(uri) {
188
+ const prefix = "sg://v2/material/";
189
+ if (typeof uri !== "string" || !uri.startsWith(prefix) || uri.length > prefix.length + 256) {
190
+ throw new McpError(ErrorCode.InvalidParams, "material resource URI must use the canonical v2 authority and path");
191
+ }
192
+ const encoded = uri.slice(prefix.length);
193
+ const reference = materialReference(encoded);
194
+ if (materialResourceUri(reference) !== uri) {
195
+ throw new McpError(ErrorCode.InvalidParams, "material resource URI is not canonical");
196
+ }
197
+ return reference;
198
+ }
199
+
200
+ export function createMaterialMcpServer({ registry = runtimeRegistry(), manifest = loadManifest() } = {}) {
201
+ assertRegistry(registry);
202
+ if (!manifest || !Array.isArray(manifest.materials)) throw new TypeError("material MCP manifest is unavailable");
203
+ const server = new McpServer({ name: "StyleGallery Material", version: "2.0.0" });
204
+ const operations = materialOperationSpecs.filter(({ name }) => exposedNames.has(name));
205
+ const operationByName = new Map(operations.map((operation) => [operation.name, operation]));
206
+
207
+ for (const operation of operations) {
208
+ server.registerTool(operation.name, {
209
+ description: descriptions[operation.name],
210
+ inputSchema: inputSchemas[operation.name],
211
+ outputSchema,
212
+ annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: false, readOnlyHint: true },
213
+ _meta: { effect_class: "NONE" },
214
+ }, async (input) => toolResponse(registry.invoke(operation.name, normalizeMaterialMcpInput(input))));
215
+ }
216
+ server.server.setRequestHandler(safeCallToolRequestSchema, async (request) => {
217
+ const operation = operationByName.get(request.params.name);
218
+ if (!operation) throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
219
+ if (request.params.arguments?.__stylegallery_unsafe_material_input__ === true) {
220
+ throw new McpError(ErrorCode.InvalidParams, "operation input must contain only own plain data");
221
+ }
222
+ const normalized = normalizeMaterialMcpInput(request.params.arguments);
223
+ const parsed = await inputSchemas[operation.name].safeParseAsync(normalized);
224
+ if (!parsed.success) throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for tool ${operation.name}`);
225
+ return toolResponse(registry.invoke(operation.name, parsed.data));
226
+ });
227
+
228
+ server.registerResource(
229
+ "stylegallery-v2-material",
230
+ new ResourceTemplate("sg://v2/material/{reference}", {
231
+ list: async () => ({
232
+ resources: manifest.materials.map((record) => ({
233
+ name: record.stable_ref,
234
+ description: "Canonical admitted StyleGallery v2 material.",
235
+ mimeType: "application/json",
236
+ uri: materialResourceUri(record.stable_ref),
237
+ })),
238
+ }),
239
+ }),
240
+ { description: "Read one canonically encoded v2 material StableRef.", mimeType: "application/json" },
241
+ async (uri, variables) => resourceContents(uri.toString(), resourceEnvelope(registry, variables.reference)),
242
+ );
243
+ server.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
244
+ const uri = request.params.uri;
245
+ const reference = materialReferenceFromUri(uri);
246
+ return resourceContents(uri, resourceEnvelope(registry, encodeURIComponent(reference)));
247
+ });
248
+ const connect = server.connect.bind(server);
249
+ server.connect = (transport) => connect(new OwnDataTransport(transport));
250
+ return server;
251
+ }
252
+
253
+ export const createMcpServer = createMaterialMcpServer;