instar 1.3.371 → 1.3.372

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.
@@ -13993,6 +13993,19 @@ export function createRoutes(ctx) {
13993
13993
  res.status(404).json({ error: 'CommitmentTracker not configured' });
13994
13994
  return;
13995
13995
  }
13996
+ // P1.5b §3.4 owner-routing (replica-targeted beacon-field PATCH forwards).
13997
+ {
13998
+ const pOrigin = typeof req.query.origin === 'string' ? req.query.origin : undefined;
13999
+ const pRoute = await resolveCommitmentRoute(req.params.id, pOrigin);
14000
+ if (pRoute === 'ambiguous') {
14001
+ res.status(409).json({ error: `Commitment id ${req.params.id} is ambiguous across machines — retry with ?origin=<machineId>` });
14002
+ return;
14003
+ }
14004
+ if (typeof pRoute === 'object') {
14005
+ await forwardMutation(res, pRoute.forward.owner, mutatePayload(req, pRoute.forward.owner, 'patch-beacon', req.body ?? {}));
14006
+ return;
14007
+ }
14008
+ }
13996
14009
  const existing = ctx.commitmentTracker.get(req.params.id);
13997
14010
  if (!existing) {
13998
14011
  res.status(404).json({ error: `Commitment ${req.params.id} not found` });
@@ -14099,11 +14112,79 @@ export function createRoutes(ctx) {
14099
14112
  * came back with the promised update," separate from `verified` which means
14100
14113
  * "config state is as promised."
14101
14114
  */
14102
- router.post('/commitments/:id/deliver', (req, res) => {
14115
+ /**
14116
+ * P1.5b §3.4 — owner-routing decision for a mutation route. Returns:
14117
+ * 'local' → the record is OWN (or the layer is dark): mutate locally.
14118
+ * {forward} → the record lives ONLY in a replica: forward to its owner.
14119
+ * 'ambiguous' → a bare id matches own AND replica rows (409, §3.1).
14120
+ * 'absent' → nowhere in the merged view (404 falls through to local).
14121
+ */
14122
+ async function resolveCommitmentRoute(id, origin) {
14123
+ if (!ctx.commitmentReplicaStore || !ctx.commitmentTracker)
14124
+ return 'local';
14125
+ const syncMod = await import('../core/CommitmentsSync.js');
14126
+ const rows = syncMod.mergeCommitmentViews({
14127
+ ownMachineId: ctx.meshSelfId ?? 'local',
14128
+ own: ctx.commitmentTracker.getAll(),
14129
+ replicas: ctx.commitmentReplicaStore.allReplicas(),
14130
+ });
14131
+ const resolved = syncMod.resolveBareId(rows, id, origin);
14132
+ if (resolved === 'ambiguous')
14133
+ return 'ambiguous';
14134
+ if (resolved === null)
14135
+ return 'absent';
14136
+ if (resolved.viewSource === 'own')
14137
+ return 'local';
14138
+ return { forward: { owner: resolved.originMachineId } };
14139
+ }
14140
+ /** Shared forward path: verdict → mapped response; queued → honest 202. */
14141
+ async function forwardMutation(res, owner, payload) {
14142
+ if (!ctx.forwardCommitmentMutate) {
14143
+ res.status(409).json({
14144
+ error: `Commitment ${payload.id} lives on ${owner} and cross-machine mutation is not enabled here`,
14145
+ });
14146
+ return;
14147
+ }
14148
+ const r = await ctx.forwardCommitmentMutate(owner, payload);
14149
+ if (r.kind === 'queued') {
14150
+ res.status(202).json({ queued: true, owner, reason: r.reason, opKey: payload.opKey });
14151
+ return;
14152
+ }
14153
+ const v = r.outcome;
14154
+ if (v.verdict === 'applied' || v.verdict === 'idempotent-noop') {
14155
+ res.json({ delivered: payload.op === 'deliver' ? true : undefined, op: payload.op, id: payload.id, owner, verdict: v.verdict, status: v.status, ...(v.staleObservation ? { staleObservation: true } : {}) });
14156
+ return;
14157
+ }
14158
+ res.status(v.verdict === 'not-found' ? 404 : 409).json({ error: `owner verdict: ${v.verdict}`, owner, status: v.status });
14159
+ }
14160
+ function mutatePayload(req, origin, op, args) {
14161
+ return {
14162
+ origin,
14163
+ id: req.params.id,
14164
+ op,
14165
+ ...(args ? { args } : {}),
14166
+ opKey: randomUUID(),
14167
+ requestedAt: new Date().toISOString(),
14168
+ callerMachineId: ctx.meshSelfId ?? 'local',
14169
+ ...(typeof req.query.observedStatus === 'string' ? { observedStatus: req.query.observedStatus } : {}),
14170
+ };
14171
+ }
14172
+ router.post('/commitments/:id/deliver', async (req, res) => {
14103
14173
  if (!ctx.commitmentTracker) {
14104
14174
  res.status(404).json({ error: 'CommitmentTracker not configured' });
14105
14175
  return;
14106
14176
  }
14177
+ const origin = typeof req.query.origin === 'string' ? req.query.origin : undefined;
14178
+ const route = await resolveCommitmentRoute(req.params.id, origin);
14179
+ if (route === 'ambiguous') {
14180
+ res.status(409).json({ error: `Commitment id ${req.params.id} is ambiguous across machines — retry with ?origin=<machineId>` });
14181
+ return;
14182
+ }
14183
+ if (typeof route === 'object') {
14184
+ const { deliveryMessageId } = req.body ?? {};
14185
+ await forwardMutation(res, route.forward.owner, mutatePayload(req, route.forward.owner, 'deliver', deliveryMessageId ? { deliveryMessageId } : undefined));
14186
+ return;
14187
+ }
14107
14188
  const { deliveryMessageId } = req.body ?? {};
14108
14189
  const updated = ctx.commitmentTracker.deliver(req.params.id, deliveryMessageId);
14109
14190
  if (!updated) {
@@ -14112,7 +14193,7 @@ export function createRoutes(ctx) {
14112
14193
  }
14113
14194
  res.json({ delivered: true, id: updated.id, commitment: updated });
14114
14195
  });
14115
- router.post('/commitments/:id/withdraw', (req, res) => {
14196
+ router.post('/commitments/:id/withdraw', async (req, res) => {
14116
14197
  if (!ctx.commitmentTracker) {
14117
14198
  res.status(404).json({ error: 'CommitmentTracker not configured' });
14118
14199
  return;
@@ -14122,6 +14203,17 @@ export function createRoutes(ctx) {
14122
14203
  res.status(400).json({ error: 'reason is required' });
14123
14204
  return;
14124
14205
  }
14206
+ // P1.5b §3.4 owner-routing (replica-targeted withdraw forwards).
14207
+ const wOrigin = typeof req.query.origin === 'string' ? req.query.origin : undefined;
14208
+ const wRoute = await resolveCommitmentRoute(req.params.id, wOrigin);
14209
+ if (wRoute === 'ambiguous') {
14210
+ res.status(409).json({ error: `Commitment id ${req.params.id} is ambiguous across machines — retry with ?origin=<machineId>` });
14211
+ return;
14212
+ }
14213
+ if (typeof wRoute === 'object') {
14214
+ await forwardMutation(res, wRoute.forward.owner, mutatePayload(req, wRoute.forward.owner, 'withdraw', { reason }));
14215
+ return;
14216
+ }
14125
14217
  const success = ctx.commitmentTracker.withdraw(req.params.id, reason);
14126
14218
  if (!success) {
14127
14219
  res.status(404).json({ error: `Commitment ${req.params.id} not found or already resolved` });
@@ -14137,11 +14229,22 @@ export function createRoutes(ctx) {
14137
14229
  * event. No-op (404) for commitments that aren't paused or are in a terminal
14138
14230
  * status.
14139
14231
  */
14140
- router.post('/commitments/:id/resume', (req, res) => {
14232
+ router.post('/commitments/:id/resume', async (req, res) => {
14141
14233
  if (!ctx.commitmentTracker) {
14142
14234
  res.status(404).json({ error: 'CommitmentTracker not configured' });
14143
14235
  return;
14144
14236
  }
14237
+ // P1.5b §3.4 owner-routing (replica-targeted resume forwards).
14238
+ const rOrigin = typeof req.query.origin === 'string' ? req.query.origin : undefined;
14239
+ const rRoute = await resolveCommitmentRoute(req.params.id, rOrigin);
14240
+ if (rRoute === 'ambiguous') {
14241
+ res.status(409).json({ error: `Commitment id ${req.params.id} is ambiguous across machines — retry with ?origin=<machineId>` });
14242
+ return;
14243
+ }
14244
+ if (typeof rRoute === 'object') {
14245
+ await forwardMutation(res, rRoute.forward.owner, mutatePayload(req, rRoute.forward.owner, 'resume'));
14246
+ return;
14247
+ }
14145
14248
  const updated = ctx.commitmentTracker.resume(req.params.id);
14146
14249
  if (!updated) {
14147
14250
  res.status(404).json({ error: `Commitment ${req.params.id} not found, not paused, or in terminal status` });