Skip to content

Commit 1ddf479

Browse files
os-zhuangclaude
andauthored
fix(cli): stamp engines.protocol into scaffolds and example apps (#4097) (#4239)
* fix(cli): stamp engines.protocol into scaffolds and example apps (#4097) Booting any example app printed the ADR-0087 grandfathering warning: the templates users copy declared no engines.protocol range, so every derived app silently opted out of the load-time protocol handshake. - os init (app/plugin/empty templates): generated objectstack.config.ts now stamps engines: { protocol: '^<PROTOCOL_MAJOR>' } — the literal major the scaffold was generated under, i.e. what the package was authored against. - examples/app-todo, app-crm, app-showcase: declare '^17' explicitly. On a future protocol major bump the handshake turns these into loud OS_PROTOCOL_INCOMPATIBLE boot failures instead of silent admits, forcing a conscious example migration — which is the feature. - init.test.ts: every template must stamp a range admitting the current major (guards the scaffold against regressing to no-range). Verified: ^17 → checkProtocolCompat status 'ok' under runtime 17.0.0; all three examples pass 'os validate'; the compiled app-todo artifact carries engines through to the boot manifest; cli suite 89 files / 917 tests green. The warn-vs-lint-failure policy question from #4097 is deliberately left unchanged: absent ranges still warn at boot ('objectstack lint' already nudges), and escalation to a compile failure can be its own decision. Closes #4097 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HrRNgrWaRtggzmrHpbomyh * docs: stamp engines.protocol into the canonical config examples The two most-copied objectstack.config.ts blocks (your-first-project, deployment/cli configuration section) now model the same ADR-0087 engines.protocol declaration the scaffolds stamp, so hand-copied configs don't regress to the no-range grandfathering path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HrRNgrWaRtggzmrHpbomyh --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 270650f commit 1ddf479

8 files changed

Lines changed: 47 additions & 0 deletions

File tree

.changeset/tender-hats-brush.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@objectstack/cli": patch
3+
---
4+
5+
`os init` scaffolds now stamp `engines: { protocol: '^<current major>' }` into the
6+
generated `objectstack.config.ts` (all three templates), so newly authored packages
7+
participate in the ADR-0087 load-time protocol handshake instead of being admitted
8+
under the "no-range" grandfathering warning. The bundled example apps (`app-todo`,
9+
`app-crm`, `app-showcase`) now declare the same range. (#4097)

content/docs/deployment/cli.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,8 @@ export default defineStack({
897897
type: 'app',
898898
name: 'My App',
899899
description: 'My ObjectStack application',
900+
// Protocol major this app is authored against (ADR-0087 load-time check).
901+
engines: { protocol: '^17' },
900902
},
901903

902904
objects: Object.values(objects),

content/docs/getting-started/your-first-project.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ export default defineStack({
113113
version: '0.1.0',
114114
type: 'app',
115115
name: 'My App',
116+
// Protocol major this app is authored against (ADR-0087 load-time check).
117+
engines: { protocol: '^17' },
116118
},
117119
// `automation` runs flows and, per ADR-0097, materializes declarative
118120
// `connectors:` entries at boot. The three generic executors below register

examples/app-crm/objectstack.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ export default defineStack({
4444
type: 'app',
4545
name: 'CRM (minimal example)',
4646
description: 'Minimal CRM workspace used by the framework to validate the metadata loading pipeline end-to-end.',
47+
// Protocol major this package is authored against (ADR-0087). The kernel
48+
// checks the range at load time and refuses a major-incompatible runtime
49+
// with a structured diagnostic instead of failing deep in a schema parse.
50+
engines: { protocol: '^17' },
4751
},
4852

4953
// Auto-resolved by the CLI; `ui` enables the Studio shell, `automation`

examples/app-showcase/objectstack.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ export default defineStack({
8383
type: 'app',
8484
name: 'ObjectStack Showcase',
8585
description: 'Kitchen-sink workspace covering all metadata types, all view types, and the major capability chains.',
86+
// Protocol major this package is authored against (ADR-0087). The kernel
87+
// checks the range at load time and refuses a major-incompatible runtime
88+
// with a structured diagnostic instead of failing deep in a schema parse.
89+
engines: { protocol: '^17' },
8690
},
8791

8892
// Capability tokens the CLI resolves to platform plugins:

examples/app-todo/objectstack.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export default defineStack({
3636
type: 'app',
3737
name: 'Todo Manager',
3838
description: 'A comprehensive Todo app demonstrating ObjectStack Protocol features including automation, dashboards, and reports',
39+
// Protocol major this package is authored against (ADR-0087). The kernel
40+
// checks the range at load time and refuses a major-incompatible runtime
41+
// with a structured diagnostic instead of failing deep in a schema parse.
42+
engines: { protocol: '^17' },
3943
},
4044

4145
// Seed Data (top-level, registered as metadata)

packages/cli/src/commands/init.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { Args, Command, Flags } from '@oclif/core';
4+
import { PROTOCOL_MAJOR } from '@objectstack/spec/kernel';
45
import chalk from 'chalk';
56
import fs from 'fs';
67
import path from 'path';
@@ -143,6 +144,8 @@ export default defineStack({
143144
type: 'app',
144145
name: '${toTitleCase(name)}',
145146
description: '${toTitleCase(name)} application built with ObjectStack',
147+
// Protocol major this app is authored against (ADR-0087 load-time check).
148+
engines: { protocol: '^${PROTOCOL_MAJOR}' },
146149
},
147150
148151
objects: Object.values(objects),
@@ -215,6 +218,8 @@ export default defineStack({
215218
type: 'plugin',
216219
name: '${toTitleCase(name)} Plugin',
217220
description: 'ObjectStack Plugin: ${toTitleCase(name)}',
221+
// Protocol major this plugin is authored against (ADR-0087 load-time check).
222+
engines: { protocol: '^${PROTOCOL_MAJOR}' },
218223
},
219224
220225
objects: Object.values(objects),
@@ -270,6 +275,8 @@ export default defineStack({
270275
type: 'app',
271276
name: '${toTitleCase(name)}',
272277
description: '',
278+
// Protocol major this app is authored against (ADR-0087 load-time check).
279+
engines: { protocol: '^${PROTOCOL_MAJOR}' },
273280
},
274281
});
275282
`,

packages/cli/test/init.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { describe, it, expect } from 'vitest';
4+
import { PROTOCOL_MAJOR } from '@objectstack/spec/kernel';
45
import fs from 'fs';
56
import os from 'os';
67
import path from 'path';
@@ -172,6 +173,20 @@ describe('scaffold rendering — round-trip', () => {
172173
fs.rmSync(tmpRoot, { recursive: true, force: true });
173174
}
174175
});
176+
177+
// #4097 — every scaffold stamps an `engines.protocol` range admitting the
178+
// protocol major it was generated under, so new projects never boot with
179+
// the ADR-0087 handshake silently skipped ("no-range" grandfathering).
180+
it('stamps engines.protocol with the current protocol major in every template', () => {
181+
for (const key of Object.keys(TEMPLATES)) {
182+
const { tmpRoot } = renderTemplate(key as keyof typeof TEMPLATES, 'my-app');
183+
const cfg = fs.readFileSync(path.join(tmpRoot, 'objectstack.config.ts'), 'utf8');
184+
expect(cfg, `template "${key}" must declare engines.protocol`).toContain(
185+
`engines: { protocol: '^${PROTOCOL_MAJOR}' }`,
186+
);
187+
fs.rmSync(tmpRoot, { recursive: true, force: true });
188+
}
189+
});
175190
});
176191

177192
describe('detectPackageManager', () => {

0 commit comments

Comments
 (0)