@ixo/topic-protocol 1.0.0-rc.1 → 1.0.0-rc.2

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 (40) hide show
  1. package/README.md +1 -1
  2. package/dist/schemas/topic-contract-body.schema.json +12 -21
  3. package/dist/schemas/topic-contract-state.schema.json +18 -93
  4. package/dist/schemas/topic-operation.schema.json +2 -2
  5. package/dist/schemas/topic-root.schema.json +5 -1
  6. package/dist/schemas/topic-shape.schema.json +5 -4
  7. package/dist/src/contracts/schemas.d.ts +394 -75
  8. package/dist/src/contracts/topic-contract.d.ts +15 -1
  9. package/dist/src/contracts/topic-contract.js.map +1 -1
  10. package/dist/src/index.d.ts +4 -4
  11. package/dist/src/index.js +1 -1
  12. package/dist/src/index.js.map +1 -1
  13. package/dist/src/projector/payload.d.ts +34 -0
  14. package/dist/src/projector/payload.js +93 -2
  15. package/dist/src/projector/payload.js.map +1 -1
  16. package/dist/src/projector/state.js +54 -0
  17. package/dist/src/projector/state.js.map +1 -1
  18. package/dist/src/projector/types.d.ts +44 -2
  19. package/dist/src/projector/types.js.map +1 -1
  20. package/dist/src/shapes/now.d.ts +2 -2
  21. package/dist/src/shapes/now.js +38 -19
  22. package/dist/src/shapes/now.js.map +1 -1
  23. package/dist/src/shapes/progress.d.ts +3 -3
  24. package/dist/src/shapes/progress.js +391 -77
  25. package/dist/src/shapes/progress.js.map +1 -1
  26. package/dist/src/shapes/recipes.js +102 -53
  27. package/dist/src/shapes/recipes.js.map +1 -1
  28. package/dist/src/shapes/resolver.js +13 -3
  29. package/dist/src/shapes/resolver.js.map +1 -1
  30. package/dist/src/shapes/state.d.ts +72 -20
  31. package/dist/src/shapes/state.js +1 -0
  32. package/dist/src/shapes/state.js.map +1 -1
  33. package/dist/src/shapes/types.d.ts +155 -4
  34. package/dist/src/shapes/types.js +3 -1
  35. package/dist/src/shapes/types.js.map +1 -1
  36. package/package.json +1 -1
  37. package/recipes/agent-delivery.json +166 -33
  38. package/recipes/registry.json +6 -6
  39. package/recipes/research-brief.json +148 -30
  40. package/recipes/verified-work-payment.json +172 -34
@@ -1 +1 @@
1
- {"version":3,"file":"now.js","sourceRoot":"","sources":["../../../src/shapes/now.ts"],"names":[],"mappings":"AAQA,SAAS,gBAAgB,CACvB,UAA6B,EAC7B,SAAiC;IAEjC,IAAI,UAAU,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,UAAU,CAAC,UAAU,CAAC,KAAK,KAAK,SAAS,CAAC,QAAQ,CAAC;IACtG,OAAO,SAAS,CAAC,eAAe,CAAC,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;AACvG,CAAC;AAED,SAAS,UAAU,CACjB,UAA6B,EAC7B,SAAiC;IAEjC,OAAO,SAAS,CAAC,WAAW;WACvB,SAAS,CAAC,iBAAiB,CAAC,QAAQ,CAAC,UAAU,CAAC,eAAe,CAAC;WAChE,gBAAgB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,YAAY,CAAC,WAAyC;IAC7D,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,YAAY,KAAK,MAAM,CAAC;QAAE,OAAO,8BAA8B,CAAC;IAC3G,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,KAAK,WAAW,IAAI,KAAK,KAAK,UAAU,CAAC;QAAE,OAAO,wBAAwB,CAAC;IACpH,OAAO,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,wBAAwB,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,QAAmC,EACnC,SAAiC;IAEjC,MAAM,WAAW,GAAG,QAAQ,CAAC,UAAU;SACpC,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC;SACnC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,SAAS,KAAK,MAAM;QACrC,CAAC,CAAC,QAAQ,CAAC,UAAU;QACrB,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,UAAU,IAAI,QAAQ,CAAC,UAAU,CAAC;IACpE,OAAO;QACL,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,SAAS;QACT,UAAU;QACV,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,WAAW;QACX,QAAQ,EAAE;YACR,aAAa,EAAE,QAAQ,CAAC,aAAa;YACrC,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB;YAC3C,WAAW,EAAE,QAAQ,CAAC,WAAW;SAClC;QACD,GAAG,CAAC,QAAQ,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;KAC9E,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"now.js","sourceRoot":"","sources":["../../../src/shapes/now.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import type { TopicProgressInputV1, TopicProgressProjectionV1 } from "./types.js";
2
- export declare function projectTopicProgress(input: TopicProgressInputV1): TopicProgressProjectionV1;
3
- export declare function replayTopicShape(input: Omit<TopicProgressInputV1, "axisStates">, initialAxisStates?: Readonly<Record<string, string>>): Readonly<Record<string, string>>;
1
+ import type { TopicProgressInputV2, TopicProgressProjectionV2 } from "./types.js";
2
+ export declare function projectTopicProgress(input: TopicProgressInputV2): TopicProgressProjectionV2;
3
+ export declare function replayTopicShape(input: Omit<TopicProgressInputV2, "axisStates">, initialAxisStates?: Readonly<Record<string, string>>): Readonly<Record<string, string>>;
@@ -1,11 +1,16 @@
1
1
  import { SHAPE_PHASES } from "./types.js";
2
+ const EMPTY_DIGEST = `sha256:${"0".repeat(64)}`;
3
+ const PAUSED_PHASES = new Set(["verifying", "deciding", "effecting", "settling", "complete"]);
2
4
  const CONDITION_PRIORITY = {
3
- disputed: 700,
4
- failed: 650,
5
- blocked: 600,
6
- waiting: 500,
7
- "needs-input": 400,
8
- "needs-acceptance": 350,
5
+ degraded: 900,
6
+ disputed: 800,
7
+ failed: 750,
8
+ expired: 700,
9
+ blocked: 650,
10
+ waiting: 600,
11
+ "needs-input": 500,
12
+ "needs-confirmation": 450,
13
+ "review-due": 300,
9
14
  "on-track": 0,
10
15
  };
11
16
  function terminal(axis, state) {
@@ -18,8 +23,89 @@ function activePhase(input) {
18
23
  if (input.status === "archived" || input.status === "redirected")
19
24
  return "dormant";
20
25
  const axes = [...(input.shape?.shape.axes ?? [])].sort((left, right) => phaseIndex(left.phase) - phaseIndex(right.phase));
21
- const incomplete = axes.find((axis) => axis.required && !terminal(axis, input.axisStates[axis.code]));
22
- return incomplete?.phase ?? "complete";
26
+ return axes.find((axis) => axis.required && !terminal(axis, input.axisStates[axis.code]))?.phase ?? "complete";
27
+ }
28
+ function resolveSubjects(subjects, input) {
29
+ return [...new Set(subjects.flatMap((subject) => subject.kind === "actor"
30
+ ? [subject.id]
31
+ : input.actorAssignments?.[subject.id] ?? []))].sort();
32
+ }
33
+ function sourceIsCurrent(source, input) {
34
+ return source.contractRevision === input.contractRevision
35
+ && source.bodyHash === input.contractBodyHash
36
+ && source.policyDigest === input.policyDigest
37
+ && typeof source.authorityProofRef === "string"
38
+ && source.authorityProofRef.length > 0;
39
+ }
40
+ function activeActors(type, withdrawal, input) {
41
+ const active = new Set();
42
+ for (const source of input.sources) {
43
+ if (!sourceIsCurrent(source, input) || source.actor === undefined)
44
+ continue;
45
+ if (source.kind === "operation" && source.type === type)
46
+ active.add(source.actor);
47
+ if (source.kind === "operation" && source.type === withdrawal)
48
+ active.delete(source.actor);
49
+ }
50
+ return [...active];
51
+ }
52
+ function requiredCount(policy) {
53
+ if (policy.mode === "any")
54
+ return 1;
55
+ if (policy.mode === "threshold")
56
+ return policy.threshold ?? Number.POSITIVE_INFINITY;
57
+ return "subjects" in policy ? policy.subjects.length : policy.signatories.length;
58
+ }
59
+ function policySatisfied(policy, actors, subjects, input) {
60
+ const resolved = resolveSubjects(subjects, input);
61
+ if (resolved.length === 0 || resolved.length < subjects.length)
62
+ return false;
63
+ const matching = resolved.filter((actor) => actors.includes(actor));
64
+ if (policy.mode === "all")
65
+ return matching.length === resolved.length;
66
+ return matching.length >= requiredCount(policy);
67
+ }
68
+ function timeUsable(input) {
69
+ const time = input.evaluationTime;
70
+ if (time === undefined || !time.trusted)
71
+ return false;
72
+ if (time.freshUntil === undefined)
73
+ return true;
74
+ return Date.parse(time.freshUntil) >= Date.parse(time.at);
75
+ }
76
+ function reached(at, input) {
77
+ return at === undefined || (timeUsable(input) && Date.parse(input.evaluationTime.at) >= Date.parse(at));
78
+ }
79
+ function confirmationSatisfied(input) {
80
+ const policy = input.activationPolicy?.confirmation;
81
+ if (policy === undefined)
82
+ return false;
83
+ return policySatisfied(policy, activeActors("confirm-setup", "withdraw-setup-confirmation", input), policy.subjects, input);
84
+ }
85
+ function assentSatisfied(input) {
86
+ const policy = input.assentPolicy;
87
+ if (policy === undefined)
88
+ return true;
89
+ return policySatisfied(policy, activeActors("record-assent", "withdraw-assent", input), policy.signatories, input);
90
+ }
91
+ function activationSatisfied(input) {
92
+ const policy = input.activationPolicy;
93
+ return policy !== undefined
94
+ && resolveSubjects(policy.editors ?? [], input).length > 0
95
+ && resolveSubjects(policy.dispute?.resolvers ?? [], input).length > 0
96
+ && policy.lifecycle !== undefined
97
+ && confirmationSatisfied(input)
98
+ && assentSatisfied(input)
99
+ && reached(policy.lifecycle.effectiveAt, input);
100
+ }
101
+ function activationPolicyComplete(input) {
102
+ const policy = input.activationPolicy;
103
+ return policy !== undefined
104
+ && resolveSubjects(policy.editors ?? [], input).length > 0
105
+ && policy.confirmation !== undefined
106
+ && resolveSubjects(policy.confirmation.subjects, input).length > 0
107
+ && policy.lifecycle !== undefined
108
+ && resolveSubjects(policy.dispute?.resolvers ?? [], input).length > 0;
23
109
  }
24
110
  function sourceMatches(source, kind, type, status) {
25
111
  if (source.kind !== kind || source.type !== type)
@@ -39,71 +125,265 @@ function gateSatisfied(gate, input) {
39
125
  }
40
126
  if (gate.kind === "binding")
41
127
  return input.bindings?.includes(gate.type) === true;
42
- if (gate.kind === "condition")
128
+ if (gate.kind === "condition") {
129
+ if (gate.states.includes("disputed") && input.dispute?.status === "open")
130
+ return true;
43
131
  return input.block !== undefined && gate.states.includes(input.block.condition);
132
+ }
44
133
  return input.sources.some((source) => sourceMatches(source, gate.kind, gate.type, gate.status));
45
134
  }
135
+ function transitionActors(transition, input) {
136
+ if (transition.code === "confirm-setup") {
137
+ return resolveSubjects(input.activationPolicy?.confirmation?.subjects ?? [], input);
138
+ }
139
+ if (transition.code === "resolve-dispute" && input.dispute !== undefined) {
140
+ return resolveSubjects([input.dispute.resolver], input);
141
+ }
142
+ if (transition.assignedTo.kind === "actor")
143
+ return [transition.assignedTo.value];
144
+ return input.actorAssignments?.[transition.assignedTo.value] ?? [];
145
+ }
46
146
  function transitionCandidate(transition, input) {
47
- const gateStatus = transition.gates.map((gate) => ({ gate, satisfied: gateSatisfied(gate, input) }));
48
- const actors = transition.assignedTo.kind === "role" ? input.actorAssignments?.[transition.assignedTo.value] : [transition.assignedTo.value];
147
+ const assignedActors = transitionActors(transition, input);
49
148
  return {
50
149
  transition,
51
- ...(actors?.[0] === undefined ? {} : { assignedActor: actors[0] }),
52
- gateStatus,
150
+ ...(assignedActors[0] === undefined ? {} : { assignedActor: assignedActors[0] }),
151
+ assignedActors,
152
+ gateStatus: transition.gates.map((gate) => ({ gate, satisfied: gateSatisfied(gate, input) })),
53
153
  };
54
154
  }
55
- function matchingTransitions(input, phase) {
155
+ function potentialTransitions(input, phase) {
56
156
  const transitions = input.shape?.shape.transitions ?? [];
57
- const recovery = transitions.filter(({ gates }) => gates.some(({ kind }) => kind === "condition"));
58
- const available = input.block === undefined ? transitions.filter(({ gates }) => gates.every(({ kind }) => kind !== "condition")) : recovery;
59
- return available
60
- .filter(({ predecessor, phase: transitionPhase }) => (input.block !== undefined || transitionPhase === phase) && input.axisStates[predecessor.axis] === predecessor.state)
157
+ const recoveryOnly = input.block !== undefined || input.dispute?.status === "open";
158
+ return transitions
159
+ .filter((transition) => {
160
+ const recovery = transition.gates.some(({ kind }) => kind === "condition");
161
+ if (recoveryOnly)
162
+ return recovery;
163
+ return !recovery && transition.phase === phase;
164
+ })
165
+ .filter(({ predecessor }) => input.axisStates[predecessor.axis] === predecessor.state)
61
166
  .map((transition) => transitionCandidate(transition, input));
62
167
  }
63
- function sourceCondition(input) {
64
- if (input.sources.some(({ status }) => status === "disputed"))
65
- return "disputed";
66
- if (input.sources.some(({ status }) => status === "failed"))
67
- return "failed";
68
- return undefined;
168
+ function missingRequirements(matches, input) {
169
+ const gates = matches.flatMap(({ gateStatus }) => gateStatus.filter(({ satisfied, gate }) => !satisfied && gate.requirement !== undefined));
170
+ const unique = new Map();
171
+ for (const { gate } of gates) {
172
+ const requirement = gate.requirement;
173
+ const responsibility = requirement.assignedTo;
174
+ const assignedActors = responsibility === undefined ? [] : resolveSubjects([
175
+ { kind: responsibility.kind, id: responsibility.value },
176
+ ], input);
177
+ unique.set(requirement.code, {
178
+ id: `requirement:${requirement.code}`,
179
+ code: requirement.code,
180
+ kind: "input",
181
+ phase: "forming",
182
+ status: "open",
183
+ priority: requirement.priority,
184
+ blocking: true,
185
+ ...(responsibility === undefined ? {} : { responsibility }),
186
+ assignedActors,
187
+ requiredAbility: "topic/write",
188
+ consequence: "shared-state",
189
+ ...((requirement.focusPath ?? (gate.kind === "field" ? gate.path : undefined)) === undefined
190
+ ? {}
191
+ : { focusPath: requirement.focusPath ?? (gate.kind === "field" ? gate.path : undefined) }),
192
+ presentation: {
193
+ labelKey: requirement.labelKey,
194
+ promptKey: requirement.promptKey,
195
+ purposeKey: requirement.purposeKey,
196
+ unlocksKey: "topic.requirement.setup.unlocks",
197
+ },
198
+ provenance: input.sources.map(({ id }) => id),
199
+ });
200
+ }
201
+ return [...unique.values()].sort((left, right) => left.priority - right.priority || left.code.localeCompare(right.code));
202
+ }
203
+ function obligationForTransition(candidate, input) {
204
+ const { transition } = candidate;
205
+ const presentation = transition.presentation;
206
+ return {
207
+ id: `transition:${transition.code}`,
208
+ code: transition.code,
209
+ kind: transition.code === "confirm-setup" ? "confirmation" : "transition",
210
+ phase: transition.phase,
211
+ status: "ready",
212
+ priority: presentation?.priority ?? 900,
213
+ blocking: transition.phase !== "working" || transition.code === "confirm-setup",
214
+ responsibility: transition.assignedTo,
215
+ assignedActors: candidate.assignedActors ?? [],
216
+ requiredAbility: transition.requiredAbility,
217
+ consequence: transition.consequence,
218
+ ...(presentation?.focusPath === undefined ? {} : { focusPath: presentation.focusPath }),
219
+ transitionCode: transition.code,
220
+ command: transition.command,
221
+ ...(transition.handoff === undefined ? {} : { handoff: transition.handoff }),
222
+ presentation: {
223
+ labelKey: presentation?.actionLabelKey ?? `topic.action.${transition.code}.label`,
224
+ promptKey: presentation?.bodyKey ?? `topic.action.${transition.code}.prompt`,
225
+ purposeKey: presentation?.purposeKey ?? `topic.action.${transition.code}.purpose`,
226
+ ...(presentation?.unlocksKey === undefined ? {} : { unlocksKey: presentation.unlocksKey }),
227
+ },
228
+ provenance: input.sources.map(({ id }) => id),
229
+ };
230
+ }
231
+ function policyInputObligation(input, code, priority, focusPath) {
232
+ const key = code.replace("setup.", "");
233
+ const editors = input.actorAssignments?.["setup-editor"] ?? [];
234
+ return {
235
+ id: `requirement:${code}`, code, kind: "input", phase: "forming", status: "open", priority,
236
+ blocking: true, responsibility: { kind: "role", value: "setup-editor" }, assignedActors: editors,
237
+ requiredAbility: "topic/write", consequence: "shared-state", focusPath,
238
+ presentation: { labelKey: `topic.requirement.${key}.label`, promptKey: `topic.requirement.${key}.prompt`, purposeKey: `topic.requirement.${key}.purpose`, unlocksKey: "topic.requirement.setup.unlocks" },
239
+ provenance: input.sources.map(({ id }) => id),
240
+ };
241
+ }
242
+ function policyObligations(input) {
243
+ const obligations = [];
244
+ if (resolveSubjects(input.activationPolicy?.editors ?? [], input).length === 0) {
245
+ obligations.push(policyInputObligation(input, "setup.editors", 600, "/activationPolicy/editors"));
246
+ }
247
+ if (input.activationPolicy?.confirmation === undefined) {
248
+ obligations.push(policyInputObligation(input, "setup.confirmation-policy", 610, "/activationPolicy/confirmation"));
249
+ }
250
+ if (input.activationPolicy?.lifecycle === undefined) {
251
+ obligations.push(policyInputObligation(input, "setup.lifecycle-policy", 620, "/activationPolicy/lifecycle"));
252
+ }
253
+ if (resolveSubjects(input.activationPolicy?.dispute?.resolvers ?? [], input).length === 0) {
254
+ obligations.push(policyInputObligation(input, "setup.dispute-resolvers", 630, "/activationPolicy/dispute/resolvers"));
255
+ }
256
+ if (input.assentPolicy !== undefined && !assentSatisfied(input)) {
257
+ obligations.push({
258
+ id: "assent:record-assent", code: "setup.assent", kind: "assent", phase: "forming", status: "open",
259
+ priority: 720, blocking: true, assignedActors: resolveSubjects(input.assentPolicy.signatories, input),
260
+ requiredAbility: "topic/record-assent", consequence: "shared-state", command: "topic.record-assent",
261
+ presentation: { labelKey: "topic.assent.label", promptKey: "topic.assent.prompt", purposeKey: "topic.assent.purpose", unlocksKey: "topic.assent.unlocks" },
262
+ provenance: input.sources.map(({ id }) => id),
263
+ });
264
+ }
265
+ const effectiveAt = input.activationPolicy?.lifecycle?.effectiveAt;
266
+ if (confirmationSatisfied(input) && assentSatisfied(input) && effectiveAt !== undefined && !reached(effectiveAt, input)) {
267
+ obligations.push({
268
+ id: "lifecycle:scheduled", code: "setup.scheduled", kind: "wait", phase: "forming", status: "waiting",
269
+ priority: 100, blocking: true, assignedActors: [], consequence: "none",
270
+ presentation: { labelKey: "topic.lifecycle.scheduled.label", promptKey: "topic.lifecycle.scheduled.prompt", purposeKey: "topic.lifecycle.scheduled.purpose", unlocksKey: "topic.lifecycle.scheduled.unlocks" },
271
+ provenance: input.sources.map(({ id }) => id),
272
+ });
273
+ }
274
+ return obligations;
275
+ }
276
+ function lifecycleState(input) {
277
+ const lifecycle = input.activationPolicy?.lifecycle;
278
+ if (lifecycle === undefined || !timeUsable(input))
279
+ return { expired: false, reviewDue: false };
280
+ const at = Date.parse(input.evaluationTime.at);
281
+ return {
282
+ expired: lifecycle.expiresAt !== undefined && at >= Date.parse(lifecycle.expiresAt),
283
+ reviewDue: lifecycle.reviewAt !== undefined && at >= Date.parse(lifecycle.reviewAt),
284
+ };
285
+ }
286
+ function contextualObligations(input, phase) {
287
+ const obligations = [];
288
+ if (!timeUsable(input))
289
+ obligations.push({
290
+ id: "safety:time-untrusted", code: "safety.time-untrusted", kind: "safety", phase, status: "unavailable",
291
+ priority: 50, blocking: PAUSED_PHASES.has(phase), assignedActors: [], consequence: "none",
292
+ presentation: { labelKey: "topic.safety.time-untrusted.label", promptKey: "topic.safety.time-untrusted.prompt", purposeKey: "topic.safety.time-untrusted.purpose" },
293
+ provenance: input.sources.map(({ id }) => id),
294
+ });
295
+ if (input.dispute?.status === "open")
296
+ obligations.push({
297
+ id: `dispute:${input.dispute.id}`, code: "dispute.resolve", kind: "dispute", phase, status: "blocked", priority: 10,
298
+ blocking: true, responsibility: { kind: input.dispute.resolver.kind, value: input.dispute.resolver.id }, assignedActors: resolveSubjects([input.dispute.resolver], input),
299
+ requiredAbility: "topic/resolve-dispute", consequence: "shared-state", transitionCode: "resolve-dispute", command: "topic.resolve-dispute",
300
+ presentation: { labelKey: "topic.dispute.resolve.label", promptKey: "topic.dispute.resolve.prompt", purposeKey: "topic.dispute.resolve.purpose", unlocksKey: "topic.dispute.resolve.unlocks" },
301
+ provenance: [input.dispute.id],
302
+ });
303
+ const lifecycle = lifecycleState(input);
304
+ if (lifecycle.expired)
305
+ obligations.push({
306
+ id: "lifecycle:setup-expired", code: "setup.expired", kind: "expiry", phase, status: "blocked", priority: 20,
307
+ blocking: true, responsibility: { kind: "role", value: "setup-editor" }, assignedActors: input.actorAssignments?.["setup-editor"] ?? [],
308
+ requiredAbility: "topic/write", consequence: "shared-state", command: "topic.edit-setup",
309
+ presentation: { labelKey: "topic.lifecycle.expired.label", promptKey: "topic.lifecycle.expired.prompt", purposeKey: "topic.lifecycle.expired.purpose", unlocksKey: "topic.lifecycle.expired.unlocks" },
310
+ provenance: input.sources.map(({ id }) => id),
311
+ });
312
+ if (input.block !== undefined)
313
+ obligations.push({
314
+ id: `block:${input.block.sourceId}`, code: input.block.reasonCode, kind: "wait", phase,
315
+ status: input.block.condition === "waiting" ? "waiting" : "blocked", priority: 30, blocking: true,
316
+ ...(input.block.target.kind === "actor" ? { responsibility: { kind: "actor", value: input.block.target.id } } : {}),
317
+ assignedActors: input.block.target.kind === "actor" ? [input.block.target.id] : [], consequence: "none",
318
+ presentation: { labelKey: "topic.block.label", promptKey: input.block.reasonCode, purposeKey: "topic.block.purpose", unlocksKey: "topic.block.unlocks" },
319
+ provenance: [input.block.sourceId],
320
+ });
321
+ if (lifecycle.reviewDue)
322
+ obligations.push({
323
+ id: "lifecycle:review-due", code: "setup.review-due", kind: "review", phase, status: "ready", priority: 1000,
324
+ blocking: false, responsibility: { kind: "role", value: "setup-editor" }, assignedActors: input.actorAssignments?.["setup-editor"] ?? [],
325
+ requiredAbility: "topic/write", consequence: "shared-state", command: "topic.review-setup",
326
+ presentation: { labelKey: "topic.lifecycle.review-due.label", promptKey: "topic.lifecycle.review-due.prompt", purposeKey: "topic.lifecycle.review-due.purpose", unlocksKey: "topic.lifecycle.review-due.unlocks" },
327
+ provenance: input.sources.map(({ id }) => id),
328
+ });
329
+ return obligations;
330
+ }
331
+ function obligationRank(obligation, input) {
332
+ if (input.status === "archived" || input.status === "redirected" || obligation.code.startsWith("safety.shape"))
333
+ return 0;
334
+ if (obligation.kind === "dispute" || input.sources.some(({ status }) => status === "failed"))
335
+ return 10;
336
+ if (obligation.kind === "expiry")
337
+ return 20;
338
+ if (obligation.kind === "wait")
339
+ return 30;
340
+ if (obligation.kind === "input")
341
+ return 40;
342
+ if (obligation.kind === "confirmation" || obligation.kind === "assent")
343
+ return 50;
344
+ if (obligation.kind === "transition")
345
+ return 60;
346
+ if (obligation.kind === "review")
347
+ return 70;
348
+ return 80;
69
349
  }
70
- function deriveCondition(input, phase, matches) {
350
+ function choosePrimary(obligations, input) {
351
+ return [...obligations].sort((left, right) => obligationRank(left, input) - obligationRank(right, input)
352
+ || left.priority - right.priority || left.code.localeCompare(right.code))[0]?.id;
353
+ }
354
+ function deriveCondition(input, obligations) {
71
355
  if (input.shape === undefined || !input.historyCompatible)
72
356
  return "degraded";
73
- const conditions = [];
74
- const observed = sourceCondition(input);
75
- if (observed !== undefined)
76
- conditions.push(observed);
77
- if (input.block !== undefined)
78
- conditions.push(input.block.condition);
79
- if (conditions.length > 0)
80
- return conditions.sort((left, right) => CONDITION_PRIORITY[right] - CONDITION_PRIORITY[left])[0] ?? "on-track";
81
- if (matches.some(({ gateStatus }) => gateStatus.some(({ gate, satisfied }) => (gate.kind === "field" || gate.kind === "field-any") && !satisfied)))
357
+ if (input.dispute?.status === "open" || input.sources.some(({ status }) => status === "disputed"))
358
+ return "disputed";
359
+ if (input.sources.some(({ status }) => status === "failed"))
360
+ return "failed";
361
+ if (obligations.some(({ kind }) => kind === "expiry"))
362
+ return "expired";
363
+ if (input.block?.condition === "blocked")
364
+ return "blocked";
365
+ if (input.block?.condition === "waiting")
366
+ return "waiting";
367
+ if (obligations.some(({ kind }) => kind === "input"))
82
368
  return "needs-input";
83
- if (phase === "forming")
84
- return "needs-acceptance";
369
+ if (obligations.some(({ kind }) => kind === "confirmation" || kind === "assent"))
370
+ return "needs-confirmation";
371
+ if (obligations.some(({ kind }) => kind === "review"))
372
+ return "review-due";
85
373
  return "on-track";
86
374
  }
87
375
  function phaseTag(input, phase) {
88
376
  const axis = input.shape?.shape.axes.find((candidate) => candidate.phase === phase);
89
- return {
90
- code: `phase:${phase}`,
91
- source: axis === undefined
92
- ? { progressRule: `phase:${phase}` }
93
- : { axis: axis.code, state: input.axisStates[axis.code] ?? axis.initialState },
94
- priority: 100,
95
- tone: phase === "complete" ? "positive" : "neutral",
96
- provenance: input.sources.map(({ id }) => id),
97
- };
377
+ return { code: `phase:${phase}`, source: axis === undefined ? { progressRule: `phase:${phase}` } : { axis: axis.code, state: input.axisStates[axis.code] ?? axis.initialState }, priority: 100, tone: phase === "complete" ? "positive" : "neutral", provenance: input.sources.map(({ id }) => id) };
98
378
  }
99
379
  function conditionTone(condition) {
100
380
  if (condition === "disputed" || condition === "failed")
101
381
  return "critical";
102
- if (condition === "blocked" || condition === "degraded")
382
+ if (condition === "blocked" || condition === "degraded" || condition === "expired")
103
383
  return "warning";
104
384
  if (condition === "waiting")
105
385
  return "info";
106
- if (condition === "needs-input" || condition === "needs-acceptance")
386
+ if (["needs-input", "needs-confirmation", "review-due"].includes(condition))
107
387
  return "attention";
108
388
  return "neutral";
109
389
  }
@@ -111,66 +391,100 @@ function stateTags(input, phase, condition) {
111
391
  const tags = [phaseTag(input, phase)];
112
392
  if (condition === "on-track")
113
393
  return tags;
114
- return [...tags, {
115
- code: `condition:${condition}`,
116
- source: { progressRule: `condition:${condition}` },
117
- priority: condition === "degraded" ? 800 : CONDITION_PRIORITY[condition],
118
- tone: conditionTone(condition),
119
- provenance: input.block === undefined ? input.sources.map(({ id }) => id) : [input.block.sourceId],
120
- }];
121
- }
122
- function reasonCode(phase, condition, candidates) {
123
- if (phase === "dormant")
124
- return "topic-dormant";
125
- if (condition !== "on-track")
126
- return `topic-${condition}`;
127
- if (phase === "complete")
128
- return candidates.some(({ transition }) => transition.completesTopic === true)
129
- ? "topic-completion-required"
130
- : "topic-complete";
131
- return `topic-${phase}`;
394
+ return [...tags, { code: `condition:${condition}`, source: { progressRule: `condition:${condition}` }, priority: CONDITION_PRIORITY[condition], tone: conditionTone(condition), provenance: input.block === undefined ? input.sources.map(({ id }) => id) : [input.block.sourceId] }];
395
+ }
396
+ function readyCandidates(matches, input) {
397
+ let candidates = matches.filter(({ gateStatus }) => gateStatus.every(({ satisfied }) => satisfied));
398
+ if (!activationPolicyComplete(input))
399
+ candidates = candidates.filter(({ transition }) => transition.code !== "confirm-setup");
400
+ if (input.dispute?.status === "open")
401
+ candidates = candidates.filter(({ transition }) => transition.code === "resolve-dispute");
402
+ const lifecycle = lifecycleState(input);
403
+ if (lifecycle.expired || !timeUsable(input))
404
+ candidates = candidates.filter(({ transition }) => !PAUSED_PHASES.has(transition.phase));
405
+ return candidates;
406
+ }
407
+ function safetyObligation(input, phase) {
408
+ if (input.status === "archived" || input.status === "redirected")
409
+ return {
410
+ id: "safety:dormant", code: "safety.dormant", kind: "safety", phase, status: "unavailable", priority: 0,
411
+ blocking: true, assignedActors: [], consequence: "none", presentation: { labelKey: "topic.safety.dormant.label", promptKey: "topic.safety.dormant.prompt", purposeKey: "topic.safety.dormant.purpose" }, provenance: input.sources.map(({ id }) => id),
412
+ };
413
+ if (input.shape === undefined || !input.historyCompatible)
414
+ return {
415
+ id: "safety:shape-unavailable", code: "safety.shape-unavailable", kind: "safety", phase, status: "unavailable", priority: 0,
416
+ blocking: true, assignedActors: [], consequence: "none", presentation: { labelKey: "topic.safety.shape-unavailable.label", promptKey: "topic.safety.shape-unavailable.prompt", purposeKey: "topic.safety.shape-unavailable.purpose" }, provenance: input.sources.map(({ id }) => id),
417
+ };
418
+ return undefined;
132
419
  }
133
420
  export function projectTopicProgress(input) {
134
421
  const phase = activePhase(input);
135
- const matches = phase === "dormant" ? [] : matchingTransitions(input, phase);
136
- const condition = deriveCondition(input, phase, matches);
137
- const suppress = phase === "dormant" || condition === "degraded";
138
- const candidates = suppress ? [] : matches.filter(({ gateStatus }) => gateStatus.every(({ satisfied }) => satisfied));
422
+ const safety = safetyObligation(input, phase);
423
+ const matches = safety === undefined ? potentialTransitions(input, phase) : [];
424
+ const missing = phase === "forming" ? missingRequirements(matches, input) : [];
425
+ const policies = phase === "forming" && missing.length === 0 ? policyObligations(input) : [];
426
+ const candidates = safety === undefined ? readyCandidates(matches, input) : [];
427
+ const transitionObligations = candidates
428
+ .filter(({ transition }) => transition.code !== "edit-setup" || missing.length > 0)
429
+ .map((candidate) => obligationForTransition(candidate, input));
430
+ const obligations = [
431
+ ...(safety === undefined ? [] : [safety]),
432
+ ...contextualObligations(input, phase),
433
+ ...missing,
434
+ ...policies,
435
+ ...transitionObligations,
436
+ ];
437
+ const condition = phase === "dormant" ? "on-track" : deriveCondition(input, obligations);
438
+ const primaryObligationId = choosePrimary(obligations, input);
139
439
  return {
140
- version: 1,
440
+ version: 2,
141
441
  topicId: input.topicId,
142
442
  topicRevision: input.topicRevision,
143
443
  contractRevision: input.contractRevision,
144
- shapeDigest: input.shape?.digest ?? "sha256:0000000000000000000000000000000000000000000000000000000000000000",
444
+ shapeDigest: input.shape?.digest ?? EMPTY_DIGEST,
145
445
  phase,
146
446
  condition,
147
- reasonCode: reasonCode(phase, condition, candidates),
447
+ reasonCode: phase === "complete" && candidates.some(({ transition }) => transition.completesTopic === true)
448
+ ? "topic-completion-required"
449
+ : primaryObligationId === undefined
450
+ ? (phase === "complete" ? "topic-complete" : `topic-${phase}`)
451
+ : `obligation:${obligations.find(({ id }) => id === primaryObligationId)?.code}`,
148
452
  axisStates: input.axisStates,
149
453
  stateTags: stateTags(input, phase, condition),
150
454
  candidates,
455
+ obligations,
456
+ ...(primaryObligationId === undefined ? {} : { primaryObligationId }),
457
+ ...(input.evaluationTime === undefined ? {} : { evaluatedAt: input.evaluationTime }),
151
458
  provenance: input.sources.map(({ id }) => id),
152
459
  ...(input.block === undefined ? {} : { block: input.block }),
460
+ ...(input.dispute === undefined ? {} : { dispute: input.dispute }),
153
461
  };
154
462
  }
155
463
  function evidenceMatches(source, transition) {
156
464
  const evidence = transition.evidence;
157
465
  if (source.kind !== evidence.kind || source.type !== evidence.type)
158
466
  return false;
159
- if (evidence.finality === undefined)
160
- return true;
161
- return source.finality === evidence.finality;
467
+ return evidence.finality === undefined || source.finality === evidence.finality;
162
468
  }
163
469
  export function replayTopicShape(input, initialAxisStates = {}) {
164
470
  const axes = input.shape?.shape.axes ?? [];
165
471
  let states = Object.fromEntries(axes.map((axis) => [axis.code, initialAxisStates[axis.code] ?? axis.initialState]));
472
+ const processedSources = [];
166
473
  for (const source of input.sources) {
167
- const replayInput = { ...input, axisStates: states };
168
- const transition = input.shape?.shape.transitions.find((candidate) => states[candidate.predecessor.axis] === candidate.predecessor.state
474
+ processedSources.push(source);
475
+ const replayInput = { ...input, sources: processedSources, axisStates: states };
476
+ if (states.contract === "proposed" && activationSatisfied(replayInput))
477
+ states = { ...states, contract: "effective" };
478
+ const transition = input.shape?.shape.transitions.find((candidate) => candidate.predecessor.axis !== "contract"
479
+ && states[candidate.predecessor.axis] === candidate.predecessor.state
169
480
  && evidenceMatches(source, candidate)
170
481
  && candidate.gates.every((gate) => gateSatisfied(gate, replayInput)));
171
482
  if (transition !== undefined)
172
483
  states = { ...states, [transition.target.axis]: transition.target.state };
173
484
  }
485
+ const finalInput = { ...input, sources: processedSources, axisStates: states };
486
+ if (states.contract === "proposed" && activationSatisfied(finalInput))
487
+ states = { ...states, contract: "effective" };
174
488
  return states;
175
489
  }
176
490
  //# sourceMappingURL=progress.js.map