@almadar/core 10.10.0 → 10.11.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.
@@ -1,4 +1,4 @@
1
- import { a as OrbitalDefinition, O as OrbitalSchema } from './schema-C4ia6wFF.js';
1
+ import { a as OrbitalDefinition, O as OrbitalSchema } from './schema-CtOoanon.js';
2
2
 
3
3
  /**
4
4
  * Event Wiring
@@ -1,6 +1,6 @@
1
- import { g as FactoryParamValue, c as FactoryConfigTier, b as FactoryConfigParam, F as FactoryCallSite, h as FactorySignature, R as RuleOverlay, p as RuleOverlayEntry, o as PresentationOverlay, q as TraitOverlay } from '../types-DzmFPbo4.js';
2
- export { a as FactoryCallSiteParams, d as FactoryEntitySignature, e as FactoryEventSignature, f as FactoryPageSignature, i as FactorySignatureCatalog, j as FactorySignatureEntityField, k as FactoryTraitSignature, m as JsonSchema, n as JsonSchemaType, J as JsonValue, O as OwnershipOverlayEntry, P as PresentationNavItem, S as SchemaFieldType, r as TraitOverlayEntry, s as TraitOverlayListener } from '../types-DzmFPbo4.js';
3
- import { g as EntityPersistence, f as EntityField, j as TraitReference } from '../trait-CKueAwP9.js';
1
+ import { g as FactoryParamValue, c as FactoryConfigTier, b as FactoryConfigParam, F as FactoryCallSite, h as FactorySignature, R as RuleOverlay, p as RuleOverlayEntry, o as PresentationOverlay, q as TraitOverlay } from '../types-COc0mLs1.js';
2
+ export { a as FactoryCallSiteParams, d as FactoryEntitySignature, e as FactoryEventSignature, f as FactoryPageSignature, i as FactorySignatureCatalog, j as FactorySignatureEntityField, k as FactoryTraitSignature, m as JsonSchema, n as JsonSchemaType, J as JsonValue, O as OwnershipOverlayEntry, P as PresentationNavItem, S as SchemaFieldType, r as TraitOverlayEntry, s as TraitOverlayListener } from '../types-COc0mLs1.js';
3
+ import { g as EntityPersistence, f as EntityField, j as TraitReference } from '../trait-BB1dc5A_.js';
4
4
  import 'zod';
5
5
  import '../expression-BUIi9ezJ.js';
6
6
  import '@almadar/patterns';
@@ -27,14 +27,32 @@ import '@almadar/patterns';
27
27
  * + input widget keyed off `inputType` + `suggestedAnswers` (and, for
28
28
  * structured types, `itemSchema` / `objectSchema`); the answer fills
29
29
  * the matching `mutationTemplate`.
30
+ *
31
+ * ### Surfacing flag
32
+ * `advanced` is `true` for questions whose tier is NOT `domain` or
33
+ * `policy` (i.e. `infra`, `presentation`, untagged, or any non-canonical
34
+ * value). The UI collapses advanced questions under a disclosure panel;
35
+ * questions with `advanced` absent or `false` render inline.
36
+ *
37
+ * ### Entity-field multiselect
38
+ * When `inputType` is `'multiselect'` and `mutationTemplate.kind` is
39
+ * `'set-orbital-entity-fields'`, the answer is the subset of
40
+ * `suggestedAnswers` the user checked. The UI maps selected names back
41
+ * to `EntityField[]` via `fieldCandidates` (keyed by field name) before
42
+ * submitting to `answersToMutations`. `fieldDescriptions` carries each
43
+ * field's semantic description for the checkbox label.
30
44
  */
31
45
  interface DomainQuestion {
32
46
  /** Unique within a session. Keys the answer record. */
33
47
  id: string;
34
48
  /** Natural-language prompt shown to the user. */
35
49
  question: string;
36
- /** One short sentence — why this question matters. */
37
- reason: string;
50
+ /**
51
+ * One short sentence explaining WHY this question is being asked —
52
+ * distinct from `helpText`. Never set to the same string as `helpText`.
53
+ * Undefined when no distinct rationale exists.
54
+ */
55
+ reason?: string;
38
56
  /** Orbital this question scopes to (matches
39
57
  * `FactoryCallSite.orbital`). */
40
58
  orbitalName: string;
@@ -54,9 +72,14 @@ interface DomainQuestion {
54
72
  * pre-fills the input, the user can confirm or override. */
55
73
  defaultValue?: DomainQuestionAnswer;
56
74
  /** Closed-enum or curated suggestions. Empty when the input is
57
- * free-form. */
75
+ * free-form. For entity-field multiselect this is the candidate
76
+ * field name list (see `fieldCandidates`). */
58
77
  suggestedAnswers?: ReadonlyArray<string>;
59
- /** Free-text help shown under the input. */
78
+ /**
79
+ * How-to-answer caption shown under the input widget. Set from
80
+ * `FactoryConfigParam.description` (the knob's own authored caption).
81
+ * Never set to the same text as `reason`.
82
+ */
60
83
  helpText?: string;
61
84
  /** Per-element schema for array-of-objects (`listOfObjects`) and
62
85
  * free-form chip lists (`tagList`). Propagated from
@@ -70,6 +93,14 @@ interface DomainQuestion {
70
93
  * `@tier` annotation — the studio treats undefined as
71
94
  * `'presentation'`. */
72
95
  tier?: FactoryConfigTier;
96
+ /**
97
+ * `true` when this question is NOT a primary domain/policy decision —
98
+ * i.e. `tier` is absent, `'infra'`, `'presentation'`, or any
99
+ * non-canonical value. The UI collapses advanced questions under a
100
+ * disclosure panel. Domain and policy questions always have
101
+ * `advanced` absent (falsy).
102
+ */
103
+ advanced?: boolean;
73
104
  /** Impact weight derived from `tier` (policy/domain high, infra medium,
74
105
  * presentation low). Drives the studio's Confidence score — answering a
75
106
  * high-weight question moves the needle more. */
@@ -78,6 +109,20 @@ interface DomainQuestion {
78
109
  * (deterministic); the studio server may boost it by prompt relevance.
79
110
  * Higher = surfaced earlier. */
80
111
  priority?: number;
112
+ /**
113
+ * Full `EntityField` candidates keyed by field name. Present only on
114
+ * entity-field multiselect questions (`inputType: 'multiselect'`,
115
+ * `mutationTemplate.kind: 'set-orbital-entity-fields'`). The UI maps
116
+ * the user's selected field names back to `EntityField[]` via this
117
+ * record before calling `answersToMutations`.
118
+ */
119
+ fieldCandidates?: Readonly<Record<string, EntityField>>;
120
+ /**
121
+ * Human-readable description for each candidate field name. Keyed by
122
+ * field name (same keys as `fieldCandidates`). The UI shows this as
123
+ * the checkbox sublabel.
124
+ */
125
+ fieldDescriptions?: Readonly<Record<string, string>>;
81
126
  }
82
127
  /**
83
128
  * Narrow set of widget kinds the studio renders.
@@ -175,7 +220,14 @@ type DomainQuestionAnswers = Readonly<Record<string, DomainQuestionAnswer>>;
175
220
  * @packageDocumentation
176
221
  */
177
222
 
178
- declare function generateQuestions(plan: ReadonlyArray<FactoryCallSite>, catalog: ReadonlyArray<FactorySignature>, ruleOverlay?: RuleOverlay): DomainQuestion[];
223
+ declare function generateQuestions(plan: ReadonlyArray<FactoryCallSite>, catalog: ReadonlyArray<FactorySignature>, ruleOverlay?: RuleOverlay,
224
+ /**
225
+ * Optional user-authored prompt from the current session. Carried
226
+ * through so downstream consumers (route handlers, ranking passes)
227
+ * can re-order questions by relevance without re-generating. Does
228
+ * not affect the emitted set — only threading and ordering.
229
+ */
230
+ _userPrompt?: string): DomainQuestion[];
179
231
  /**
180
232
  * Pick a widget for a config-key question from the param's typed
181
233
  * declaration + its structural carriers. The type tag is lifted
@@ -1,5 +1,5 @@
1
1
  // src/factory/questions/generate.ts
2
- function generateQuestions(plan, catalog, ruleOverlay) {
2
+ function generateQuestions(plan, catalog, ruleOverlay, _userPrompt) {
3
3
  const out = [];
4
4
  const ruleCapabilities = collectRuleCapabilities(ruleOverlay);
5
5
  for (const call of plan) {
@@ -38,12 +38,14 @@ function buildConfigKeyQuestion(call, trait, param) {
38
38
  const question = `${label}?`;
39
39
  const callSiteOverride = callSiteOverrideValue(call, trait.name, param.key);
40
40
  const effectiveDefault = callSiteOverride !== void 0 ? callSiteOverride : param.default;
41
- const reason = param.description ?? `Customizes "${param.key}" on the ${trait.name} trait. ` + (effectiveDefault !== void 0 ? `Default: ${stringifyDefault(effectiveDefault)}.` : `No default \u2014 leave blank to inherit the factory's behavior.`);
41
+ const helpText = param.description;
42
+ const syntheticReason = `Customizes "${param.key}" on the ${trait.name} trait. ` + (effectiveDefault !== void 0 ? `Default: ${stringifyDefault(effectiveDefault)}.` : `No default \u2014 leave blank to inherit the factory's behavior.`);
43
+ const reason = helpText !== void 0 ? void 0 : syntheticReason;
44
+ const isAdvanced = !isPrimaryTier(param.tier);
42
45
  const out = {
43
46
  id: `${call.orbital}.${trait.name}.${param.key}`,
44
47
  orbitalName: call.orbital,
45
48
  question,
46
- reason,
47
49
  inputType: deriveInputType(param),
48
50
  mutationTemplate: {
49
51
  kind: "set-trait-override-config",
@@ -52,6 +54,12 @@ function buildConfigKeyQuestion(call, trait, param) {
52
54
  configKey: param.key
53
55
  }
54
56
  };
57
+ if (reason !== void 0) {
58
+ out.reason = reason;
59
+ }
60
+ if (helpText !== void 0) {
61
+ out.helpText = helpText;
62
+ }
55
63
  if (effectiveDefault !== void 0) {
56
64
  out.defaultValue = effectiveDefault;
57
65
  }
@@ -67,8 +75,8 @@ function buildConfigKeyQuestion(call, trait, param) {
67
75
  if (param.tier) {
68
76
  out.tier = param.tier;
69
77
  }
70
- if (param.description) {
71
- out.helpText = param.description;
78
+ if (isAdvanced) {
79
+ out.advanced = true;
72
80
  }
73
81
  const weight = tierWeight(param.tier);
74
82
  out.weight = weight;
@@ -89,6 +97,9 @@ function tierWeight(tier) {
89
97
  return 1;
90
98
  }
91
99
  }
100
+ function isPrimaryTier(tier) {
101
+ return tier === "domain" || tier === "policy";
102
+ }
92
103
  function callSiteOverrideValue(call, traitName, configKey) {
93
104
  const override = call.params.traitOverrides?.[traitName];
94
105
  if (!override?.config) return void 0;
@@ -144,27 +155,42 @@ function entityFieldQuestions(call, signature) {
144
155
  if (signature.entities.length === 0) return [];
145
156
  const entity = signature.entities[0];
146
157
  const entityName = call.params.entityName ?? entity.name;
147
- const defaultFields = entity.fields.map(
148
- signatureFieldToEntityField
149
- );
150
- const weight = tierWeight("domain");
151
- return [
152
- {
153
- id: `${call.orbital}.__entityFields`,
154
- orbitalName: call.orbital,
155
- question: `What fields should a ${entityName} have?`,
156
- reason: `Defines the data model for ${entityName}. Add domain-specific fields or confirm the canonical defaults.`,
157
- inputType: "fieldList",
158
- mutationTemplate: {
159
- kind: "set-orbital-entity-fields",
160
- orbitalName: call.orbital
161
- },
162
- defaultValue: defaultFields,
163
- tier: "domain",
164
- weight,
165
- priority: weight
158
+ const candidateFields = entity.fields.map(signatureFieldToEntityField);
159
+ const fieldCandidates = {};
160
+ const fieldDescriptions = {};
161
+ const candidateNames = [];
162
+ for (let i = 0; i < entity.fields.length; i++) {
163
+ const sig = entity.fields[i];
164
+ const ef = candidateFields[i];
165
+ const name = sig.name;
166
+ candidateNames.push(name);
167
+ fieldCandidates[name] = ef;
168
+ if (sig.description) {
169
+ fieldDescriptions[name] = sig.description;
166
170
  }
167
- ];
171
+ }
172
+ const weight = tierWeight("domain");
173
+ const q = {
174
+ id: `${call.orbital}.__entityFields`,
175
+ orbitalName: call.orbital,
176
+ question: `Which fields should a ${entityName} have?`,
177
+ reason: `Defines the data model for ${entityName}. Pick the fields that matter for your use case.`,
178
+ inputType: "multiselect",
179
+ mutationTemplate: {
180
+ kind: "set-orbital-entity-fields",
181
+ orbitalName: call.orbital
182
+ },
183
+ suggestedAnswers: candidateNames,
184
+ defaultValue: candidateNames,
185
+ fieldCandidates,
186
+ tier: "domain",
187
+ weight,
188
+ priority: weight
189
+ };
190
+ if (Object.keys(fieldDescriptions).length > 0) {
191
+ q.fieldDescriptions = fieldDescriptions;
192
+ }
193
+ return [q];
168
194
  }
169
195
  function signatureFieldToEntityField(f) {
170
196
  const base = {
@@ -207,23 +233,26 @@ function capabilityQuestions(call, signature, ruleCapabilities) {
207
233
  const seen = /* @__PURE__ */ new Set();
208
234
  const out = [];
209
235
  for (const trait of signature.traits) {
236
+ const policyKnobKeys = new Set(
237
+ trait.overridableConfigKeys.filter((p) => p.tier === "policy").map((p) => p.key)
238
+ );
210
239
  for (const cap of trait.capabilities) {
211
240
  if (seen.has(cap)) continue;
212
241
  seen.add(cap);
213
242
  if (ruleCapabilities.has(cap)) continue;
243
+ if (policyKnobKeys.has(cap)) continue;
214
244
  out.push({
215
245
  id: `${call.orbital}.capability.${cap}`,
216
246
  orbitalName: call.orbital,
217
247
  question: capabilityPrompt(cap, entityName),
218
- reason: capabilityReason(cap, trait),
248
+ helpText: capabilityHelp(cap, trait),
219
249
  capability: cap,
220
- inputType: "capability",
250
+ inputType: "boolean",
221
251
  mutationTemplate: {
222
252
  kind: "set-rule",
223
253
  capability: cap,
224
254
  appliesTo: [entityName]
225
255
  },
226
- suggestedAnswers: ["yes", "skip"],
227
256
  // Capabilities encode governance/policy decisions — high impact.
228
257
  weight: 3,
229
258
  priority: 3
@@ -233,10 +262,11 @@ function capabilityQuestions(call, signature, ruleCapabilities) {
233
262
  return out;
234
263
  }
235
264
  function capabilityPrompt(capability, entity) {
236
- return `Should "${capability}" apply to ${entity}?`;
265
+ const label = humanizeKey(capability);
266
+ return `Enable ${label} for ${entity}?`;
237
267
  }
238
- function capabilityReason(capability, trait) {
239
- return `The "${trait.name}" trait advertises capability "${capability}". Answer "yes" to wire a matching rule into the schema.`;
268
+ function capabilityHelp(capability, trait) {
269
+ return `Turns on the "${capability}" behavior from the ${trait.name} trait. Say yes to wire a governance rule into the schema.`;
240
270
  }
241
271
  function findSignature(catalog, organism, orbital) {
242
272
  return catalog.find((s) => s.organism === organism && s.orbital === orbital);