@volter/twin-standard 1.0.104 → 1.0.106

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.
@@ -96,8 +96,12 @@ export declare function assessPack(packDir: string, options?: AssessmentOptions)
96
96
  teardownVerified: boolean;
97
97
  retainedReadback: boolean;
98
98
  clock: {
99
+ beforeStop?: string | null | undefined;
100
+ afterResume?: string | null | undefined;
101
+ retentionVerified?: boolean | undefined;
99
102
  at: string;
100
103
  advanceAfterRun?: string;
104
+ mode?: undefined;
101
105
  } | {
102
106
  mode: string;
103
107
  };
@@ -80,8 +80,12 @@ export declare function runConsumerWorkflow(packDir: string, options: ConsumerOp
80
80
  teardownVerified: boolean;
81
81
  retainedReadback: boolean;
82
82
  clock: {
83
+ beforeStop?: string | null | undefined;
84
+ afterResume?: string | null | undefined;
85
+ retentionVerified?: boolean | undefined;
83
86
  at: string;
84
87
  advanceAfterRun?: string;
88
+ mode?: undefined;
85
89
  } | {
86
90
  mode: string;
87
91
  };
@@ -163,6 +163,8 @@ export async function runConsumerWorkflow(packDir, options) {
163
163
  let upAttempted = false;
164
164
  let retainedReadback = false;
165
165
  let teardownVerified = false;
166
+ let clockBeforeStop = null;
167
+ let clockAfterResume = null;
166
168
  try {
167
169
  if (compiledCli)
168
170
  nodeVersion = (await execute('node-version', 'node', ['--version'])).trim();
@@ -180,9 +182,20 @@ export async function runConsumerWorkflow(packDir, options) {
180
182
  await run('clock-advance', ['clock', 'advance', recipe.clock.advanceAfterRun]);
181
183
  await run('inspect-timed-result', ['run', '--', ...recipe.retention.readback]);
182
184
  }
185
+ if (recipe.clock) {
186
+ clockBeforeStop = (await run('clock-before-stop', ['clock', 'show'])).trim();
187
+ const instant = Date.parse(clockBeforeStop);
188
+ if (!Number.isFinite(instant) || new Date(instant).toISOString() !== clockBeforeStop)
189
+ throw new Error('Declared customer clock is not frozen before stop');
190
+ }
183
191
  const before = JSON.parse(await run('log-before-stop', ['log', '--json']));
184
192
  await run('stop', ['down']);
185
193
  await run('resume', ['up']);
194
+ if (recipe.clock) {
195
+ clockAfterResume = (await run('clock-after-resume', ['clock', 'show'])).trim();
196
+ if (clockAfterResume !== clockBeforeStop)
197
+ throw new Error('Resume lost or changed the declared customer clock');
198
+ }
186
199
  const resumed = JSON.parse(await run('log-after-resume', ['log', '--json']));
187
200
  if (JSON.stringify(before) !== JSON.stringify(resumed))
188
201
  throw new Error('Resume changed recorded application state before readback');
@@ -218,7 +231,7 @@ export async function runConsumerWorkflow(packDir, options) {
218
231
  sourceInputsSha256: consumerSourceDigest(packDir), recipeSha256: createHash('sha256').update(JSON.stringify(recipe)).digest('hex'),
219
232
  fixture: existsSync(join(packDir, 'journeys/first-use.json')) ? 'journeys/first-use.json' : `standard/clients/${vendor}.first-use.json`, appDir: resolve(options.appDir), appName: basename(options.appDir),
220
233
  commands, failures, teardownVerified, retainedReadback,
221
- clock: recipe.clock ?? { mode: 'wall' },
234
+ clock: recipe.clock ? { ...recipe.clock, ...('readback' in recipe.retention ? { beforeStop: clockBeforeStop, afterResume: clockAfterResume, retentionVerified: retainedReadback && clockBeforeStop !== null && clockBeforeStop === clockAfterResume } : {}) } : { mode: 'wall' },
222
235
  ...('none' in recipe.retention ? { retentionNotApplicable: recipe.retention.none } : {}),
223
236
  node: nodeVersion,
224
237
  evaluatorRuntime: { nodeCompatibility: process.versions.node, bun: process.versions.bun ?? null },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-standard",
3
- "version": "1.0.104",
3
+ "version": "1.0.106",
4
4
  "description": "The Protocol 3 standard for Volter twin packs: the criteria a pack is graded by, the grader, the gate a pack repository and the twin catalog run on every change and submission, and the spec derivation the grade checks against. Pin a version: every place a pack is judged judges by that standard.",
5
5
  "keywords": [
6
6
  "twin",
@@ -75,7 +75,7 @@
75
75
  "yaml": "2.9.0"
76
76
  },
77
77
  "peerDependencies": {
78
- "@volter/world-core": "3.0.112",
78
+ "@volter/world-core": "3.0.114",
79
79
  "@volter/world-runtime": "*",
80
80
  "playwright": "^1.63.0"
81
81
  },
package/src/consumer.ts CHANGED
@@ -149,6 +149,8 @@ export async function runConsumerWorkflow(packDir: string, options: ConsumerOpti
149
149
  let upAttempted = false;
150
150
  let retainedReadback = false;
151
151
  let teardownVerified = false;
152
+ let clockBeforeStop: string | null = null;
153
+ let clockAfterResume: string | null = null;
152
154
  try {
153
155
  if (compiledCli) nodeVersion = (await execute('node-version', 'node', ['--version'])).trim();
154
156
  await run('init', ['init', '--twins', vendor]);
@@ -163,9 +165,18 @@ export async function runConsumerWorkflow(packDir: string, options: ConsumerOpti
163
165
  await run('clock-advance', ['clock', 'advance', recipe.clock.advanceAfterRun]);
164
166
  await run('inspect-timed-result', ['run', '--', ...recipe.retention.readback]);
165
167
  }
168
+ if (recipe.clock) {
169
+ clockBeforeStop = (await run('clock-before-stop', ['clock', 'show'])).trim();
170
+ const instant = Date.parse(clockBeforeStop);
171
+ if (!Number.isFinite(instant) || new Date(instant).toISOString() !== clockBeforeStop) throw new Error('Declared customer clock is not frozen before stop');
172
+ }
166
173
  const before = JSON.parse(await run('log-before-stop', ['log', '--json']));
167
174
  await run('stop', ['down']);
168
175
  await run('resume', ['up']);
176
+ if (recipe.clock) {
177
+ clockAfterResume = (await run('clock-after-resume', ['clock', 'show'])).trim();
178
+ if (clockAfterResume !== clockBeforeStop) throw new Error('Resume lost or changed the declared customer clock');
179
+ }
169
180
  const resumed = JSON.parse(await run('log-after-resume', ['log', '--json']));
170
181
  if (JSON.stringify(before) !== JSON.stringify(resumed)) throw new Error('Resume changed recorded application state before readback');
171
182
  await run('retained-readback', ['run', '--', ...recipe.retention.readback]);
@@ -192,7 +203,7 @@ export async function runConsumerWorkflow(packDir: string, options: ConsumerOpti
192
203
  sourceInputsSha256: consumerSourceDigest(packDir), recipeSha256: createHash('sha256').update(JSON.stringify(recipe)).digest('hex'),
193
204
  fixture: existsSync(join(packDir, 'journeys/first-use.json')) ? 'journeys/first-use.json' : `standard/clients/${vendor}.first-use.json`, appDir: resolve(options.appDir), appName: basename(options.appDir),
194
205
  commands, failures, teardownVerified, retainedReadback,
195
- clock: recipe.clock ?? { mode: 'wall' },
206
+ clock: recipe.clock ? { ...recipe.clock, ...('readback' in recipe.retention ? { beforeStop: clockBeforeStop, afterResume: clockAfterResume, retentionVerified: retainedReadback && clockBeforeStop !== null && clockBeforeStop === clockAfterResume } : {}) } : { mode: 'wall' },
196
207
  ...('none' in recipe.retention ? { retentionNotApplicable: recipe.retention.none } : {}),
197
208
  node: nodeVersion,
198
209
  evaluatorRuntime: { nodeCompatibility: process.versions.node, bun: process.versions.bun ?? null },