-
5a616d5:
create-objectstacknow closes with a "Created files" summary derived from a walk of the finished project directory, so it names everything the run wrote — including the files written after the template copy (#10323).The old summary was the template copy's own list, printed before
<pm> installand beforenpx skills add. Measured against publishedcreate-objectstack@17.1.0(create-objectstack demo-app, then a full walk of the result): 12 entries printed, 18,045 paths on disk, 18,033 of them unreachable from the summary —AGENTS.md,.github/copilot-instructions.md,pnpm-lock.yaml,skills-lock.json,node_modules/, and two ~968 KB trees of agent instructions at.agents/skills/andagent/skills/.That mattered because the same run ends with the
skillsCLI printing "Review skills before use; they run with full agent permissions." Advice to review files the run never named, at paths it never showed, is advice a newcomer cannot act on — the wrong failure direction for a security-flavoured warning.The list could not have been correct where it stood: two of the three write phases belong to other processes, and the
skillsinstaller's destination set moves with its releases, not ours. Reading the directory afterwards makes the summary self-correcting instead. Large directories collapse to one line carrying their path, entry count and size, so the bulk stays reviewable without 18,000 lines of output, and the paths the skills installer created are marked⚠ skillswith the permissions warning tied to them.Same run, after the change: 20 entries printed, 0 written paths unreachable.
-
cec9d23: Fix
create-objectstack's startup banner hardcoding◆ Create ObjectStack v6.xregardless of the package's real, released version — eleven majors stale, on the first line of output a newcomer ever sees (#10325). The banner now callsreadCliVersion(), the same reader.version()already used, instead of a literal string.Dropping the real version in without recomputing the box's padding would have reintroduced the same defect one line later — the border is a fixed run of
═computed for the 4-characterv6.x, and a longer real version (v17.1.0is 7 characters) would push the right border out of alignment (the sibling bug fixed in #10322, one function away in the same file). The box now derives its width from the version string's plain length and widens the frame — never truncates — for a version long enough to need more room; ordinary versions still render at the historical box size.No behaviour change beyond the printed banner.
-
3a3f209: Tell a newcomer that the
blankstarter ships no app, so an empty Console reads as the intended starting point rather than a broken install (#10317).Measured on a real scaffold-and-boot (
create-objectstack my-app -t blank, published 17.1.0 packages,objectstack dev --ui):GET /api/v1/meta/appreturns the two platform apps (Setup, Account) and nothing of the project's own, whileGET /api/v1/data/my_app_noteserves the scaffolded object the whole time. The template shipssrc/objects/only — deliberately, as every scaffolder template in this repo does — but nothing the newcomer could reach said so, andpnpm devadvertises the Console URL on every boot.Documentation only: a new "The Console" section in the generated
README.mdnaming the Console path, the consequence, andsrc/apps/*.app.tsas the remedy. No change to what the scaffolder writes intosrc/. -
7bf3fb7: Point every documentation link in these packages' published READMEs — and in the project
create-objectstackscaffolds — at the canonical docs originhttps://objectstack.ai, replacing thedocs.objectstack.aispelling.Both spellings reach the same pages (the alias redirects to the apex, path-preserving), so no link was broken. The reason it needs a release rather than an in-repo fix alone: a README ships inside the npm tarball, so the version already on npm keeps showing the old host to every reader of the package page until a new one is published.
-
675ab57: First-run polish: a brand-new scaffold's very first
pnpm installno longer reports two unmet peer dependencies (#10326).Reproduced on a clean scaffold from published
create-objectstack@17.1.0— no lockfile,node_modulesremoved, nothing configured by the user — and again on the second scaffold path,objectstack init. Both printed the same two:✕ unmet peer better-call Installed: 1.4.0 Wanted: 1.3.7: @better-auth/scim@1.7.0-rc.1 ✕ unmet peer better-sqlite3 Installed: 13.0.3 Wanted: ^12.0.0: better-auth@1.7.1Nothing was broken — but it is the first screen a newcomer sees, and there is nothing they did to cause it or can do about it.
better-sqlite3: the pin is right and the upstream range is stale — so it is widened, not corrected. better-auth 1.7.1 declaresbetter-sqlite3as an optional peer at^12.0.0, and it governs exactly one configuration: a raw better-sqlite3Databasehanded to better-auth'sdatabaseoption, which its Kysely dialect then drives. ObjectStack never takes that path —AuthManager.createDatabaseConfig()returnscreateObjectQLAdapterFactory(dataEngine), and everybetter-sqlite3use underplugin-authis knex'sclient: 'better-sqlite3'beneath ObjectQL. Measured anyway on the configuration the range does govern: better-auth 1.7.1 withdatabase: new Database(':memory:'), runninggetMigrations().runMigrations(),signUpEmail,signInEmailand adapterfindOne/update/delete, is green on better-sqlite3 13.0.3 and byte-for-byte equivalent on 12.11.1. The same probe withDatabase.prototype.prepareneutered fails, so that green is the driver's and not an unexercised path. Pinning our own^13.0.3declarations back to^12would downgrade a native module across the platform to satisfy a range measurement shows is simply behind.@better-auth/scim: the rc pin stays, and onebetter-callcopy is the correct tree.npm view @better-auth/scim dist-tagsreadslatest: '1.7.1', but stable 1.7.x ships the rc.2 whole-model rewrite, so adopting it is a separate migration rather than a version bump; the exact1.7.0-rc.1pin is deliberate. The rc peers an exactbetter-call@1.3.7while better-auth 1.7.1 depends on1.4.0— and a better-auth plugin has to share the host's better-call instance, so the single 1.4.0 copy every install already resolves is right, not a skew to repair. This declaration retires together with the rc pin.What changed, and what deliberately did not. Both remedies are pnpm
peerDependencyRules.allowedVersionsentries, scoped<declaring package>><peer>so each widens exactly one declaration. They ship inside the scaffold — the bundledpnpm-workspace.yamltemplate and the oneobjectstack initrenders — because a block in this repo's own workspace file does not travel with published packages.allowedVersionschanges what pnpm reports, never what it resolves: measured on both scaffold paths, the lockfile is byte-identical with and without it (0 lines of diff), and no dependency version, range or resolution moved anywhere. This repo's own resolutions are untouched. -
e85182d: Converge the blank scaffold template's
README.mddocs links on the ruled canonical origin,https://objectstack.ai(maintainer ruling, 2026-08-21: 「这个仓的文档站规范 URL 是 https://objectstack.ai」; enforced byCANONICAL_DOCS_ORIGINinscripts/check-published-readme-links.mjs). The template previously linked the accepted-but-unratifieddocs.objectstack.aialias in three places, which disagreed with the rootREADME.md's already- canonical spelling — so a singlenpm create objectstack@latestrun handed the user two different hostnames for the same docs site. -
aea1e64: Fix the declared bin (
bin/create-objectstack.js) being tracked non-executable in git. It carries a#!/usr/bin/env nodeshebang and is pnpm's link target for thecreate-objectstackcommand, but was committed100644instead of100755— matching the sibling declared binpackages/cli/bin/run.js, which was already tracked executable.Patch bump: this is a packaging-mode correction with no content, API or behavior change (the blob hash is identical) — it only fixes how the file is tracked in git and therefore how it is packed for npm.
-
818e027: Fix
objectstack init's closing "Created files" summary omittingpnpm-lock.yaml/package-lock.jsonandnode_modules/(#10557).The summary used to be printed from a list accumulated while the template files were written — before
<pm> installran — so it could never name what the package manager wrote.initnow prints it after the install attempt (succeeded or failed) from a walk of the finished project directory, reusingcreate-objectstack'screated-summary.ts(now published as thecreate-objectstack/created-summarysubpath) instead of a second copy of the same renderer. -
afe1c4e: fix(cli): declare the four
@better-auth/utilspeer skews a freshly scaffolded project reports (#10931)Both scaffold paths emit a
peerDependencyRules.allowedVersionsblock whose stated purpose is that a brand-new project's firstpnpm installdoes not open with a peer-skew report. It declared two skews and left four showing:├─┬ @better-auth/core 1.7.1 │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0 ├─┬ @better-auth/scim 1.7.0-rc.1 │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0 ├─┬ @better-auth/oauth-provider 1.7.1 │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0 └─┬ @better-auth/sso 1.7.1 └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0@better-auth/core,/oauth-provider,/scimand/ssoeach peer an exact@better-auth/utils@0.4.2. The 0.5.0 they are handed comes frombetter-call@1.4.0— better-auth's own HTTP layer — which depends on^0.5.0;@objectstack/plugin-authnames the four as direct dependencies without naming utils, so pnpm satisfies their peer from better-call's copy instead of better-auth's own exact 0.4.2 dependency.Measured compatible before widening, not assumed. Those four import three symbols in total:
base64/base64Url(@better-auth/utils/base64),createHash(/hash) and, in core only,createRandomStringGenerator(/random). 0.5.0 declares all three with identical signatures;/randomis unchanged apart from formatting,/base64swapsnew Uint8Array(data)for a helper that isnew Uint8Array(data)on non-strings, and/hashonly widens its input coercion for views not backed by a plainArrayBuffer. Run against the input shapes those call sites actually pass, the two versions agree on every value; run end to end — better-auth with thesso,oauth-providerandscimplugins — a tree where the four resolve 0.5.0 and one where they resolve 0.4.2 produce the same transcript: sign-up, sign-in, session, both OAuth metadata documents, the RFC 7636 PKCE challenge, and the SCIM and SSO endpoint outcomes.A resolution change was measured too, and rejected: pinning utils back to 0.4.2 clears the four lines only by dragging
better-call@1.4.0off its own declared^0.5.0— manufacturing one real range violation to silence four benign ones.Four scoped entries, one per declaring package, matching the block's convention that each rule widens exactly one declaration.
allowedVersionssuppresses the report only: the lockfile a scaffold resolves is byte-identical with and without the block. The version is spelled0.5.0exactly rather than0.5, so a future0.6.0reports again instead of inheriting this finding.Both scaffold paths —
objectstack init(rendered by the CLI) andnpx create-objectstack(a copied template file) — are changed together, andpackages/cli/test/scaffold-workspace-consistency.test.tsgains a limb that compares the peer maps the two produce, so they cannot drift apart again. -
21756b3: fix(create-objectstack): converge scaffold docs on the canonical host and drop the last two dead monorepo references (#10990, #11022)
A freshly scaffolded project shipped a handful of text lines a reader with only their own project — no monorepo, no
docs/adr/, no issue tracker — could not follow:templates/AGENTS.mdlinkedhttps://objectstack.com/docs, a domain that is not this project's docs site at all (not even a redirecting alias).templates/blank/Dockerfileandtemplates/blank/docker-compose.ymlboth linkedhttps://docs.objectstack.ai/..., an accepted-but-unratified alias. All three now point at the ruled canonical origin,https://objectstack.ai(maintainer ruling, 2026-08-21).templates/blank/README.mdcitedADR-0097and named "the ObjectStack framework repo" as the home ofskills/— both rewritten self-contained, keeping the fact each was carrying: the connector-materialization line now links the public Automation → Connectors page, and the skills line now names the followablenpx skills add objectstack-ai/objectstack/skillsinstall the scaffolder's own closing output already uses.
packages/create-objectstack/src/starter-comments-self-contained.test.ts(#10324) gains two pin obligations these two fixes call for: a host-convergence assertion driven by the sameshippedFiles()walker that already enumerates everything a scaffold ships (no other repo gate's population reaches these template files —check:published-readme-linksreads publishable packages' published markdown only), and a fifthMONOREPO_ONLYpattern that catches a prose-shaped reference to this repo ("the ObjectStack framework repo") the first four, syntax-shaped patterns could not. The self-retiringEXCLUDEDentry forblank/README.mdis removed now that the file cites nothing monorepo-only. -
568de19: Scaffolded projects declare an explicit empty
packages: []in theirpnpm-workspace.yaml(#10933). Both scaffold paths render it —renderPnpmWorkspaceYamlinobjectstack init, and the bundledblanktemplatenpx create-objectstackcopies.The file was deliberately keyless so it would act purely as a settings file. That intent is now written down rather than inferred from a missing key, and writing it down is what fixes a first-command failure: pnpm 9.x and 10.0–10.4 parse
pnpm-workspace.yamlbefore they readengines, so they refused a brand-new project outright withERROR packages field missing or emptynaming a file the user never wrote and giving no hint that the cause is their pnpm version — and no
engines.pnpmfloor could reach them, because they never got as far as the engines check. Measured, one clean install per pnpm version, each with its own store:pnpm before after 9.15.9, 10.0.0, 10.4.0 ERROR packages field missing or emptyERR_PNPM_UNSUPPORTED_ENGINE, naming>=10.1510.5.0–10.14.0 ERR_PNPM_UNSUPPORTED_ENGINEunchanged 10.15.0, 10.34.5, 11.22.0 installs installs, byte-identical pnpm-lock.yamlSo every unsupported pnpm now reports the same actionable cause, and supported pnpm is unaffected: the empty key was measured equivalent to omission on 10.15.0, 10.34.5 and 11.22.0 — identical lockfile bytes, identical
node_modules/.modules.yamlonce the run-localprunedAt/storeDirfields are dropped, identicalpnpm ls -r --depth -1, and an identical second-install "Already up to date".The declaration is an empty list on purpose.
packages: ['.']satisfies the same parsers but declares the project root a workspace member — a monorepo root — which a single-package scaffold is not, and which reads to the next author (human or AI) as an invitation to add member packages to an app.engines.pnpmis unchanged at>=10.15. -
8d21f7a: Fix
create-objectstack's closing "Next steps" and install-failure remedy hardcodingnpmregardless of which package manager the run actually used (#10322).detectPackageManager()already preferspnpmand falls back tonpmonly whenpnpmis unreachable — confirmed still true at HEAD, and confirmed empirically: a real run withpnpmonPATHinstalls withpnpm(pnpm-lock.yaml, "Done in … using pnpm vX") and then told the newcomer to runnpm run dev/npm run validatenext, a package manager the run never touched. The detected package manager is now read once, up front, and reused consistently for the install command, the install-failure remedy, and every line of "Next steps" — so the printed guidance always names the tool the run actually used, in both thepnpmand thenpm-fallback case.Also names
validate— the step the generatedAGENTS.mdcalls unskippable — in the "Getting started" section of the generatedblanktemplate's README, not only in its later "Verify your changes" section, so a newcomer reading top-to-bottom sees it at first touch.No install behaviour changes: the scaffolder still installs by default and still supports
--skip-install; this is a messaging-only fix. -
9d101d2: Declare a pnpm floor (
engines.pnpm: ">=10.15") in thepackage.jsonboth scaffolders write, so an unsupported pnpm reports its own version instead of an error about a file the user never wrote.Both scaffold paths emit a settings-only
pnpm-workspace.yamlwith nopackages:key. Early pnpm 10 refuses that file outright —pnpm installexits 1 withERROR packages field missing or emptybefore resolving a single dependency, so a brand-new project could not be installed at all. Measured on the rendered shape, one clean install per pnpm version, each with its own store:pnpm before after 10.0.0 – 10.4.0 packages field missing or emptyunchanged — see below 10.5.0 – 10.14.0 packages field missing or emptyERR_PNPM_UNSUPPORTED_ENGINE, naming the expected range>= 10.15.0 installs installs The floor is a diagnosis, not a repair: pnpm 10.0.0–10.4.0 parse
pnpm-workspace.yamlbefore they readengines, so they still print the raw workspace error. Closing that remaining sliver requires deciding what a single-package scaffold should declare underpackages:, which is tracked separately and deliberately not decided here.engines.pnpmrather than apackageManagerstamp: npm, yarn and bun ignoreengines.pnpmentirely, so the scaffold keeps working for all four package managersobjectstack inithands off to. ApackageManager: "pnpm@x.y.z"stamp would declare the project pnpm-only (corepack-driven yarn refuses to run in such a project) and pin one exact version that goes stale on every pnpm release — and it buys nothing on 10.0–10.4, which reach the workspace error before reading that field either.No existing project is affected; this only changes what a newly scaffolded
package.jsoncontains. -
6d441e4: Correct the pnpm boundary the blank template states for
allowBuilds, and gate the two scaffold paths against each other (#10498, #10499).packages/create-objectstack/src/templates/blank/pnpm-workspace.yamlis copied verbatim into every scaffolded project, so its header comment is prose that ships inside the user's own repository. It saidallowBuildsneeds pnpm >= 10.31 and thatonlyBuiltDependenciescovers pnpm 10.0–10.30. Measured on a probe depending onesbuild@0.28.2, with a workspace file carrying onlyallowBuilds, one clean install per pnpm version and each with its own--store-dir(isolation matters — pnpm's side-effects cache will otherwise hand a later run a build an earlier run performed, and it reads as "the key worked"):pnpm allowBuildsalone10.15.0 – 10.25.0 ignored — build not run 10.26.0 honoured — build ran 10.28.0 – 10.33.0 honoured — build ran So the floor is 10.26.0 and the older-key band is 10.0–10.25. A user on pnpm 10.28 was being told by the file in front of them that their pnpm cannot read the key it is in fact reading. Both load-bearing claims in that comment were correct and are unchanged: both keys are needed, and pnpm 11 reads only
allowBuilds. No setting, no assertion and no install behaviour changes — the renderedonlyBuiltDependencies/allowBuildsvalues are byte-identical.The reason it was wrong for so long is the second half of this change.
objectstack initrenders the same file fromrenderPnpmWorkspaceYaml()inpackages/cli, it was corrected to the measured numbers separately, and each package's ratchets are package-local — so neither could ever fail for the other file's regression, and the two scaffold paths shipped contradictory prose about the same rule with every gate green.packages/cli/test/scaffold-workspace-consistency.test.tsnow compares the two rendered outputs: the packages each key actually grants a build to, and the pnpm versions each file actually names for each key. It was confirmed failing against the live divergence before this correction landed.Bumped
patchrather than left out: the corrected text is user-visible — it is delivered into every new project — while nothing executable moves. -
ecd06f6: Rewrite the scaffolded project's starter comments so a newcomer can actually follow them (#10324).
objectstack.config.tsandsrc/objects/note.object.tsare the first two files opened after scaffolding, and between them they cited four ADR identifiers, one bare issue number and the path of a release-time script in this monorepo — none of which ship in, or are linked from, a scaffolded project.// per ADR-0097read as a reference the reader was failing to follow rather than as the context it was meant to be.The explanations are kept and made self-contained; only the dead ends are gone. Each now states the fact the identifier stood for — the protocol range is checked before anything loads and was stamped to match the installed version rather than hand-tuned;
automationmust stay wheneverplugins:lists a connector or the executors have nowhere to register; a declarativemcpstdio transport is denied by default; the org-wide default is required so the baseline is an authored decision — and points at the public docs page that covers it in full. The blankDockerfilelikewise stops pointing at a file in this repo and points at the self-hosting guide it already links.A pin (
starter-comments-self-contained.test.ts) keeps it that way from both sides: no shipped template file may cite an ADR identifier, a bare issue number or a repo script path, and the facts those references carried must still be stated — so the comments cannot be "fixed" by deleting them. It also resolves every canonical-origin docs URL in the shipped tree againstcontent/docs, because a link that 404s is the same defect one level out.
-
1eb28a1: Retire the five remote content templates from the scaffolder's catalog.
todo,compliance,content,contractsandprocurementwere delisted from the official ObjectStack template marketplace and are no longer maintained, but the CLI carried its own hardcoded catalog and never learned that:--helprecommended all five by name with marketing descriptions, and theAvailable:line on a bad-toffered them too.blank(bundled, offline) is now the whole catalog, so the help text advertises only what is actually supported.- Asking for one of the five by name —
-t todoin an old script or tutorial — is refused with a message that says the template was retired, instead of the generic "Unknown template" error that reads as a typo. - The GitHub tarball-fetch path that served the remote templates is removed
along with its
tardependency; nothing else reached it.
Note this corrects the catalog at HEAD only. Already-published versions keep advertising the retired templates until a new version of
create-objectstackis released.
-
4906c90: Fix scaffolded projects describing themselves as the blank template (#9263)
rewriteProjectIdentityrewroteid/namespace/namein bothobjectstack.config.tsandobjectstack.manifest.jsonfrom the project name, but leftdescriptionuntouched — every scaffolded project carried the blank template's own line verbatim ("Minimal ObjectStack environment — a clean slate for building."), confidently wrong rather than empty, and printed by the first command the getting-started flow tells people to run (os validate).The scaffolder now drops
descriptionfrom both files instead of rewriting it. There is nothing but the project name to derive a replacement from, and a name-derived sentence (e.g. "Support Desk — an ObjectStack environment.") would be a bare restatement of thename/displayNamerow already shown — worse than no sentence at all.os validatealready omits the description line entirely when the field is unset, so a freshly scaffolded project now prints cleanly:Support Desk v0.1.0instead of
Support Desk v0.1.0 Minimal ObjectStack environment — a clean slate for building. -
f2f09e4: fix(create-objectstack): the scaffolded Dockerfile pins the runtime image to the CLI that builds the artifact, instead of
latestunder a comment saying to pin (#9017)src/templates/blank/DockerfileshippedFROM ghcr.io/objectstack-ai/objectstack:latestdirectly beneath a comment instructing the reader to "pin the tag to the@objectstack/cliversion in your package.json so the runtime matches the CLI that built the artifact" — an instruction the scaffold itself did not follow. Every app made withnpx create-objectstackshipped that contradiction from day one, anddocker/README.md's tag table already scopeslatestto quick starts while documentingX.Y.Zas the production pin.Measured on scaffolded output rather than the template's bytes, before the fix:
emitted package.json cli range : ^17.0.0 emitted Dockerfile FROM : FROM ghcr.io/objectstack-ai/objectstack:latest agreement (tag vs cli range) : DISAGREEThe tag is resolved after
install, from the installed CLI — not from the generatedpackage.json. That file carries a caret RANGE, and the two are not interchangeable: npm resolves^17.0.0to the newest 17.x, so pinning the range's floor would ship a runtime image older than the CLI that built the artifact — breaking the same promise in a new way. The rolling:17tag does match the range's float window but is exactly what the tag table tells production not to use. The resolved version is the only value that makes the sentence true, and it is the rule the repo already applies for this purpose in.github/workflows/scaffold-e2e.yml("Pin the runtime's CLI to the SAME version the generated project actually resolved to — NOT a hardcodedlatest").Both halves move together. Pinning the line while leaving an imperative to pin by hand would relocate the contradiction rather than remove it, so the comment above the
FROMline is replaced in the same rewrite. With--skip-installthere is no resolved version: the tag stayslatestand the comment keeps telling the reader to pin — which is true on that path, because there the user really must do it by hand.The regression proof asserts on scaffolded output, never on the template: it scaffolds with the real copy/sync/pin path, plants an installed CLI whose version is deliberately not the range's floor (the normal case, and the one that a package.json-derived tag would get wrong), and checks the emitted
FROMtag against the emittedpackage.jsonrange with a satisfies-check rather than equality..github/workflows/scaffold-e2e.ymlnow reads the tag it builds its local runtime image under out of the generated Dockerfile instead of hardcoding:latest. Those were two hand-matched literals; had they skewed, Docker would have quietly pulled the last published image instead of the one built from this checkout, and the job's own stated hermeticity would have been false while it stayed green. -
0a5adba: fix(create-objectstack): the blank template's
specVersionstops shipping eleven majors stale, and the version-time sync covers every declared surface on every template (#9264)The one bundled template declared the platform it targets in two places that disagreed by eleven majors:
file key was objectstack.manifest.jsonspecVersion^6.0.0objectstack.config.tsengines.protocol^17scripts/sync-template-versions.mjsre-stamped the config key and the template's@objectstack/*dependency ranges, and never opened the manifest at all. Soengines.protocoltracked every major bump whilespecVersionsat at the value it held when the script was written — and a greensync-template-versionsrun was never evidence about it, because the script's failure mode was loud for the keys it covered and mute for the key it did not.This is not confined to the registry contract.
create-objectstackcopies the manifest into every scaffolded project, rewritingname,displayNameandnamespaceand droppingdescription— it has never touchedspecVersion. So every project scaffolded since v7 was stamped with a^6.0.0spec range while installing@objectstack/spec@^17.0.0.The two keys are two facts, and the fix keeps them apart.
engines.protocolis the ADR-0087 D1 runtime handshake range and carries the protocol major (^17).specVersionis documented byTemplateManifestSchemaas the "Compatible@objectstack/specsemver range" and carries the package range (^17.0.0) — the same value the script already writes into the template's own@objectstack/specdependency, so the manifest and thepackage.jsonnow state one fact once. They agree on the major only because the spec package's major and the protocol major are kept in lockstep; they are stamped from two different values.Deleting the key was not available:
specVersionis required byTemplateManifestSchema, and every shipped manifest is parsed against it bycheck:template-manifests.Two structural changes, because one-key-one-file coverage is what let this sit:
- the sync script's file list is now discovered, not hard-coded — templates
are found by walking
src/templates/, the same waycheck-template-manifestsfinds the manifests it parses, so a second template is covered on the day it lands; - every stamp is required. A template whose file is missing, whose stamp is
absent, or whose
package.jsondeclares no@objectstack/*dependency is a hard failure naming the path — never a skip. A skipped stamp is indistinguishable from a synced one in the log, which is the invisibility this fixes.
The manifest is rewritten as text rather than parsed and re-serialized:
objectstack.manifest.jsonkeepsscaffold.variablescompact on one line, andJSON.stringify(…, null, 2)would reformat unrelated structure on every release.CI coverage lands as four per-template ratchets in
template-consistency.test.ts, generalized offblankonto the same directory walk — including the invariant that catches this exact class: the manifest'sspecVersionmust equal the@objectstack/specrange the template actually installs. Either file alone can be self-consistently stale; only comparing them catches a stamp that covered one and not the other. - the sync script's file list is now discovered, not hard-coded — templates
are found by walking
-
e47b342: feat!: require Node.js 22 — promise the runtime we actually test (#3825)
Every published package declared
engines.node: ">=18.0.0". Node 18 reached end-of-life on 2025-04-30 and Node 20 on 2026-04-30, so the compatibility promise covered two runtimes nobody patches — and, after #3830 moved CI to Node 22, two runtimes nothing in this repo verifies.That left the promise and the evidence with no overlap at all:
Node version What CI validates every PR on 22 What release.ymlpublishes from22 What every shipped Docker image runs ( docker/Dockerfile,blanktemplate, self-hosting docs)22 What engines.nodepromised users>=18 engines.nodeis now>=22.0.0across all 50 manifests. This is the honest floor: it is the only runtime the packages are built, tested and shipped on.If you are on Node 22 or newer, nothing changes. Node 24 (Active LTS since 2025-10-28) and Node 26 both satisfy the new range.
If you are on Node 18 or 20, upgrade to Node 22+. Both are past end-of-life and receive no security patches:
nvm install 22 && nvm use 22npm and pnpm surface an unsatisfied
enginesas anEBADENGINEwarning, not a hard failure, so an existing install will not break the moment you upgrade — but the package is no longer tested on that runtime, and the failures are the kind that do not announce themselves. #3812 is the worked example: a native dependency whoseenginesrequired a newer Node loaded anyway on the older one and then killed the test worker at the process level, with no JS error and a summary that still said "passed".If your CI pins Node, pin it to 22 as well — running your gates on a runtime your dependencies no longer support is exactly the split this change closes.
The "Node 18+" prerequisite was restated in ten user-facing places (
README.md,CONTRIBUTING.md, the getting-started and deployment docs, the todo example, and theobjectstack-platformskill'scompatibilityfield). All now say 22. Changelogs and ADRs are historical records and were left alone.
-
9f060e5: chore(deps)!: better-auth 1.7.0-rc.2 (account identity restructuring) + the production-dependency batch from #3517
better-auth 1.7.0-rc.1 → 1.7.0-rc.2 across the family (
better-auth,@better-auth/core,@better-auth/oauth-provider,@better-auth/sso, and the adapter/telemetry overrides).@better-auth/scimdeliberately stays on 1.7.0-rc.1 — rc.2 replaces its whole model (code-defined connections; thescimProvidermodel and the generate-token endpoint are gone), which is a feature migration, not a version bump. Its peer range accepts rc.2 core, and the advisory that forced the original pin (GHSA-j8v8-g9cx-5qf4) is still fixed.BREAKING — account identity. better-auth renamed
account.accountIdtoaccount.providerAccountIdand added a REQUIREDaccount.issuer; sign-in now resolves accounts by(issuer, providerAccountId).- FROM
fields: { accountId: 'account_id' }→ TOfields: { issuer: 'issuer', providerAccountId: 'account_id' }. The provider account id keeps itsaccount_idcolumn — only the better-auth-side name moved — andsys_accountgains anissuercolumn. - FROM
internalAdapter.createAccount({ providerId, accountId, … })→ TOcreateAccount({ providerId, issuer, providerAccountId, … }). A local password account carries the issuer better-auth mints for itself,local:credential. - FROM
client.auth.accounts.unlink({ providerId, accountId })→ TOunlink({ accountId }), whereaccountIdis now the account ROW id (theidfromaccounts.list()), matching better-auth's narrowed body.accounts.list()returnsissuer+providerAccountIdin place ofaccountId.
Existing deployments: rows written before 1.7 have no issuer and are invisible to sign-in until stamped. The auth plugin now runs an idempotent boot-time backfill that stamps what it can derive —
local:credentialfor password accounts,local:oauth:<providerId>for configured social providers, and the registered IdP's realissfromsys_sso_providerfor federated ones. Accounts from a federated IdP that is no longer registered cannot be derived; they are logged with their provider id and row count rather than guessed, and those users cannot sign in through that provider until the row is stamped with the IdP's issuer or removed so a fresh login re-links it.Also required by 1.7:
SecondaryStoragegained two mandatory methods, both now implemented over the kernel cache service —getAndDelete(single-use verification values) andincrement(fixed-window rate-limit counter;rateLimit.storage: 'secondary-storage'throws at boot without it).The rest of #3517's production-dependency batch rides along:
@oclif/core4.13.0,@hono/node-server2.0.12,hono4.12.32,tar7.5.22,jose6.2.4,pinyin-pro3.28.2, plus the private docs app's fumadocs/next/react bumps. - FROM
-
2e836de: chore(packaging): CHANGELOG.md ships in every npm tarball (#4261)
The AGENTS.md post-task checklist requires breaking changesets to carry their FROM → TO migration because "this text ships to consumers as
CHANGELOG.mdinside the npm package and is what an upgrading agent greps after the tombstone error." That delivery path was severed for 68 of the 69 publishable packages: npm packspackage.json/README*/LICENSE*unconditionally but — unlike older npm versions — notCHANGELOG.md, and the canonical"files": ["dist", "README.md"]whitelist never named it. Measured on npm 10.9.7:npm pack --dry-runon@objectstack/typesshipped 3 files while its 70KBCHANGELOG.mdstayed behind. Only@objectstack/speclisted it explicitly.The tombstone-error scenario is precisely the one where the repo is out of reach — the upgrading agent has
node_modulesand nothing else — so the migration text has to ride in the tarball. Every publishable package now declaresCHANGELOG.mdinfiles, and the canonical whitelist is["dist", "README.md", "CHANGELOG.md"].The other half is the gate:
check:published-filesgains a fifth invariant, COMPLETE — a whitelist that fails to coverCHANGELOG.mdfails the always-required lint job, so the next package cannot silently sever the path again.@objectstack/spec's per-package EXTRA_ENTRIES exemption dissolves into the canonical set.Consumer-visible change: one more file per install (the package's changelog, e.g. 70.8KB for
@objectstack/types), andgrep -r "removed key" node_modules/@objectstack/*/CHANGELOG.mdnow finds the migration it was promised. -
4e9e184: chore(deps): OSV security batch — bump tar to ^7.5.21 (GHSA-r292-9mhp-454m) and js-yaml to ^5.2.2 (GHSA-pm4m-ph32-ghv5)
Both are declared-range bumps to the patched releases, so downstream installs resolve the fixed versions from the published manifests, not just this workspace's lockfile. The same batch clears the remaining transitive advisories (next 16.2.11 in apps/docs; workspace overrides for brace-expansion, sharp, react-router, @sveltejs/kit, @hono/node-server) — those live in pnpm-workspace.yaml and the private docs app, which do not ship.
-
8d41998: fix(create-objectstack): scaffolding a remote template no longer produces a project that cannot build (#4926)
npx create-objectstack@latest my-app -t todo(andcompliance,content,contracts,procurement) generated a project that failedobjectstack buildimmediately — 5 of the 6 offered templates. Only the bundledblankworked.The scaffolder read the template's original namespace from
objectstack.manifest.json, and that filename names two different documents. The bundled template's is app-shaped and carriesnamespace; a remote template's is the template-registry document ($schema: …/template-manifest.json) and carries none — its namespace lives only inobjectstack.config.ts. So the value came backundefinedfor every remote template and the object-name rewrite was skipped, while the config'snamespace:was rewritten anyway. The result wasnamespace: 'my_app'sitting next toname: 'todo_task', which the${namespace}_${shortName}rule rejects. Across the five templates, 74 object names were left unrewritten.objectstack.config.tsis now the authority for the template namespace (it holds the very literal the scaffolder overwrites, so the two cannot disagree), with the manifest as fallback. The rewrite also verifies itself: any surviving stale prefix throws at the scaffold, naming the files and lines, instead of surfacing as a build failure on the user's first command. -
7309c81: chore(cli,create-objectstack): scaffolds no longer name a driver (#4065)
os initand thecreate-objectstackblank template both listed@objectstack/driver-memoryin the generateddependencies. It was the only driver named, which read as an endorsement — "this is the driver your app runs on" — when it is in fact the last-resort rung of the dev step-down (nativebetter-sqlite3→ WASM SQLite → mingo). A new project's first impression of the data layer should not be the engine that enforces no primary keys, no uniqueness, noNOT NULLand no column types.It was also redundant:
@objectstack/runtimealready depends ondriver-sql,driver-sqlite-wasmanddriver-memory, and every script in both scaffolds runs through the CLI, which carries all four. Removing the line changes nothing a generated project can do —objectstack devstill resolves SQLite by default, andOS_DATABASE_URLstill selects Postgres / MySQL / MongoDB.Docs updated to match: the "packages you depend on" table in Your first project no longer lists a driver row (it now says where drivers come from), and the Memory Driver section of Database Drivers documents the opt-in persistence default, carries a migration callout for the old
'auto'behaviour, and points test authors at in-memory SQLite. That section also claimed "Data is lost when the process exits", which was simply false while'auto'was the default — it wrote a file into the working directory.
-
8d41998: fix(create-objectstack): scaffolding a remote template no longer produces a project that cannot build (#4926)
npx create-objectstack@latest my-app -t todo(andcompliance,content,contracts,procurement) generated a project that failedobjectstack buildimmediately — 5 of the 6 offered templates. Only the bundledblankworked.The scaffolder read the template's original namespace from
objectstack.manifest.json, and that filename names two different documents. The bundled template's is app-shaped and carriesnamespace; a remote template's is the template-registry document ($schema: …/template-manifest.json) and carries none — its namespace lives only inobjectstack.config.ts. So the value came backundefinedfor every remote template and the object-name rewrite was skipped, while the config'snamespace:was rewritten anyway. The result wasnamespace: 'my_app'sitting next toname: 'todo_task', which the${namespace}_${shortName}rule rejects. Across the five templates, 74 object names were left unrewritten.objectstack.config.tsis now the authority for the template namespace (it holds the very literal the scaffolder overwrites, so the two cannot disagree), with the manifest as fallback. The rewrite also verifies itself: any surviving stale prefix throws at the scaffold, naming the files and lines, instead of surfacing as a build failure on the user's first command.
-
2e836de: chore(packaging): CHANGELOG.md ships in every npm tarball (#4261)
The AGENTS.md post-task checklist requires breaking changesets to carry their FROM → TO migration because "this text ships to consumers as
CHANGELOG.mdinside the npm package and is what an upgrading agent greps after the tombstone error." That delivery path was severed for 68 of the 69 publishable packages: npm packspackage.json/README*/LICENSE*unconditionally but — unlike older npm versions — notCHANGELOG.md, and the canonical"files": ["dist", "README.md"]whitelist never named it. Measured on npm 10.9.7:npm pack --dry-runon@objectstack/typesshipped 3 files while its 70KBCHANGELOG.mdstayed behind. Only@objectstack/speclisted it explicitly.The tombstone-error scenario is precisely the one where the repo is out of reach — the upgrading agent has
node_modulesand nothing else — so the migration text has to ride in the tarball. Every publishable package now declaresCHANGELOG.mdinfiles, and the canonical whitelist is["dist", "README.md", "CHANGELOG.md"].The other half is the gate:
check:published-filesgains a fifth invariant, COMPLETE — a whitelist that fails to coverCHANGELOG.mdfails the always-required lint job, so the next package cannot silently sever the path again.@objectstack/spec's per-package EXTRA_ENTRIES exemption dissolves into the canonical set.Consumer-visible change: one more file per install (the package's changelog, e.g. 70.8KB for
@objectstack/types), andgrep -r "removed key" node_modules/@objectstack/*/CHANGELOG.mdnow finds the migration it was promised. -
7309c81: chore(cli,create-objectstack): scaffolds no longer name a driver (#4065)
os initand thecreate-objectstackblank template both listed@objectstack/driver-memoryin the generateddependencies. It was the only driver named, which read as an endorsement — "this is the driver your app runs on" — when it is in fact the last-resort rung of the dev step-down (nativebetter-sqlite3→ WASM SQLite → mingo). A new project's first impression of the data layer should not be the engine that enforces no primary keys, no uniqueness, noNOT NULLand no column types.It was also redundant:
@objectstack/runtimealready depends ondriver-sql,driver-sqlite-wasmanddriver-memory, and every script in both scaffolds runs through the CLI, which carries all four. Removing the line changes nothing a generated project can do —objectstack devstill resolves SQLite by default, andOS_DATABASE_URLstill selects Postgres / MySQL / MongoDB.Docs updated to match: the "packages you depend on" table in Your first project no longer lists a driver row (it now says where drivers come from), and the Memory Driver section of Database Drivers documents the opt-in persistence default, carries a migration callout for the old
'auto'behaviour, and points test authors at in-memory SQLite. That section also claimed "Data is lost when the process exits", which was simply false while'auto'was the default — it wrote a file into the working directory.
-
e47b342: feat!: require Node.js 22 — promise the runtime we actually test (#3825)
Every published package declared
engines.node: ">=18.0.0". Node 18 reached end-of-life on 2025-04-30 and Node 20 on 2026-04-30, so the compatibility promise covered two runtimes nobody patches — and, after #3830 moved CI to Node 22, two runtimes nothing in this repo verifies.That left the promise and the evidence with no overlap at all:
Node version What CI validates every PR on 22 What release.ymlpublishes from22 What every shipped Docker image runs ( docker/Dockerfile,blanktemplate, self-hosting docs)22 What engines.nodepromised users>=18 engines.nodeis now>=22.0.0across all 50 manifests. This is the honest floor: it is the only runtime the packages are built, tested and shipped on.If you are on Node 22 or newer, nothing changes. Node 24 (Active LTS since 2025-10-28) and Node 26 both satisfy the new range.
If you are on Node 18 or 20, upgrade to Node 22+. Both are past end-of-life and receive no security patches:
nvm install 22 && nvm use 22npm and pnpm surface an unsatisfied
enginesas anEBADENGINEwarning, not a hard failure, so an existing install will not break the moment you upgrade — but the package is no longer tested on that runtime, and the failures are the kind that do not announce themselves. #3812 is the worked example: a native dependency whoseenginesrequired a newer Node loaded anyway on the older one and then killed the test worker at the process level, with no JS error and a summary that still said "passed".If your CI pins Node, pin it to 22 as well — running your gates on a runtime your dependencies no longer support is exactly the split this change closes.
The "Node 18+" prerequisite was restated in ten user-facing places (
README.md,CONTRIBUTING.md, the getting-started and deployment docs, the todo example, and theobjectstack-platformskill'scompatibilityfield). All now say 22. Changelogs and ADRs are historical records and were left alone.
-
9f060e5: chore(deps)!: better-auth 1.7.0-rc.2 (account identity restructuring) + the production-dependency batch from #3517
better-auth 1.7.0-rc.1 → 1.7.0-rc.2 across the family (
better-auth,@better-auth/core,@better-auth/oauth-provider,@better-auth/sso, and the adapter/telemetry overrides).@better-auth/scimdeliberately stays on 1.7.0-rc.1 — rc.2 replaces its whole model (code-defined connections; thescimProvidermodel and the generate-token endpoint are gone), which is a feature migration, not a version bump. Its peer range accepts rc.2 core, and the advisory that forced the original pin (GHSA-j8v8-g9cx-5qf4) is still fixed.BREAKING — account identity. better-auth renamed
account.accountIdtoaccount.providerAccountIdand added a REQUIREDaccount.issuer; sign-in now resolves accounts by(issuer, providerAccountId).- FROM
fields: { accountId: 'account_id' }→ TOfields: { issuer: 'issuer', providerAccountId: 'account_id' }. The provider account id keeps itsaccount_idcolumn — only the better-auth-side name moved — andsys_accountgains anissuercolumn. - FROM
internalAdapter.createAccount({ providerId, accountId, … })→ TOcreateAccount({ providerId, issuer, providerAccountId, … }). A local password account carries the issuer better-auth mints for itself,local:credential. - FROM
client.auth.accounts.unlink({ providerId, accountId })→ TOunlink({ accountId }), whereaccountIdis now the account ROW id (theidfromaccounts.list()), matching better-auth's narrowed body.accounts.list()returnsissuer+providerAccountIdin place ofaccountId.
Existing deployments: rows written before 1.7 have no issuer and are invisible to sign-in until stamped. The auth plugin now runs an idempotent boot-time backfill that stamps what it can derive —
local:credentialfor password accounts,local:oauth:<providerId>for configured social providers, and the registered IdP's realissfromsys_sso_providerfor federated ones. Accounts from a federated IdP that is no longer registered cannot be derived; they are logged with their provider id and row count rather than guessed, and those users cannot sign in through that provider until the row is stamped with the IdP's issuer or removed so a fresh login re-links it.Also required by 1.7:
SecondaryStoragegained two mandatory methods, both now implemented over the kernel cache service —getAndDelete(single-use verification values) andincrement(fixed-window rate-limit counter;rateLimit.storage: 'secondary-storage'throws at boot without it).The rest of #3517's production-dependency batch rides along:
@oclif/core4.13.0,@hono/node-server2.0.12,hono4.12.32,tar7.5.22,jose6.2.4,pinyin-pro3.28.2, plus the private docs app's fumadocs/next/react bumps. - FROM
-
4e9e184: chore(deps): OSV security batch — bump tar to ^7.5.21 (GHSA-r292-9mhp-454m) and js-yaml to ^5.2.2 (GHSA-pm4m-ph32-ghv5)
Both are declared-range bumps to the patched releases, so downstream installs resolve the fixed versions from the published manifests, not just this workspace's lockfile. The same batch clears the remaining transitive advisories (next 16.2.11 in apps/docs; workspace overrides for brace-expansion, sharp, react-router, @sveltejs/kit, @hono/node-server) — those live in pnpm-workspace.yaml and the private docs app, which do not ship.
-
3f218e4: feat(create-objectstack): the blank scaffold ships the three generic connector executors by default
npm create objectstacknow generates anobjectstack.config.tsthat wires therest,openapi, andmcpconnector executor plugins (ADR-0022/0023/0024 + ADR-0097) intoplugins:, alongsiderequires: ['automation']. This closes the last authoring gap in the ADR-0097 promise that integrations are expressible and executable as pure metadata: an author (human or AI) can now add a declarativeconnectors:entry namingprovider: 'rest' | 'openapi' | 'mcp'and have it materialize into a live, dispatchable connector at boot — with no host-code edit.plugins:—new ConnectorRestPlugin(),new ConnectorOpenApiPlugin(),new ConnectorMcpPlugin()(zero-arg = contribute the provider factory only).requires: ['automation']— the automation service performs the materialization and owns the registry the executors register into. It is also a hard dependency of the connector plugins, so a scaffold that lists them inplugins:without it fails boot; automation ships transitively via@objectstack/cli.- deps —
@objectstack/connector-rest,@objectstack/connector-openapi,@objectstack/connector-mcp. - Security (#3055): declarative
mcpstdio transports stay denied by default — opt in per host withnew ConnectorMcpPlugin({ declarativeStdio: ['node'] }).
Brand connectors (Slack, …) remain marketplace/opt-in.
-
83e8f7d: feat(mcp): decouple the stdio auto-start switch from the HTTP surface + surface the MCP endpoint on
os devboot (#3167)The MCP HTTP surface (
/api/v1/mcp) and the long-lived stdio transport used to share one env var:OS_MCP_SERVER_ENABLED=trueturned the HTTP surface on and silently auto-started the stdio transport — which bridges the raw metadata service- data engine with no per-request principal (unscoped). An operator setting it to "make sure MCP is on" got an unscoped transport as a side effect.
@objectstack/types— newresolveMcpStdioAutoStart(). Stdio auto-start is now its own switch,OS_MCP_STDIO_ENABLED(default off);OS_MCP_SERVER_ENABLEDgoverns only the HTTP surface. The legacyOS_MCP_SERVER_ENABLED=truetrigger still starts stdio for one release, flagged as deprecated.=falseis unchanged (it only ever gated HTTP).@objectstack/mcp—MCPServerPlugin.start()gates stdio on the new switch and logs a one-time deprecation warning when started via the legacy alias.@objectstack/cli—os devnow prints the MCP endpoint, the agent-skill URL, and a ready-to-pasteclaude mcp addcommand on boot (gated on the HTTP surface being on), so the "an agent operates the app it's building" loop is discoverable at dev time.create-objectstack— the blank scaffold README documents that the app is itself an MCP server (the serve side), distinct from the consume-side connector.
-
3b6ef8a: Scaffolded projects ship with a
.gitignoreagain —npx create-objectstackproduced none, leavingnode_modules/and.envun-ignored for every new user.npm pack/pnpm packstrip.gitignorefrom a tarball unconditionally, at every depth. The blank template committed one atsrc/templates/blank/.gitignoreand the build faithfully copied it todist/templates/blank/.gitignore, butfiles: ["dist"]publishing dropped it on the way to the registry — so the file was present in the repo, present in every local build, and absent from all 11 files of a real scaffold. Verified against the published 15.1.1 tarball, which shipsdist/templates/blank/.dockerignoreand no.gitignore.The template is now committed as
_gitignore(a name npm does not strip) and restored to.gitignorewhen the template is copied, via aTEMPLATE_FILE_ALIASESmap in the newtemplate-copy.ts. Only.gitignoreis aliased: the strip list is.gitignoreand.npmrc, not "every dotfile" —.dockerignorepacks fine and stays literal.The restored ignore rules also cover
.env/.env.*, which they never did. The template README has users writeOS_AUTH_SECRETandOS_SECRET_KEYinto a.env, anddocker-compose.ymlcalls that file "never committed" — but only the prose said so, and.dockerignorewas the only file that listed it.A packing ratchet in
template-consistency.test.tsguards both halves: it packs the real package, scaffolds from the extracted tarball with the real copy logic, and asserts every template file lands under its intended name. Source-level assertions cannot see this class of bug — the file only vanishes at publish. -
3a8ce9d: fix(create-objectstack): the blank scaffold declares pnpm build approvals, so a fresh
pnpm installno longer exits 1 on pnpm 11pnpm 11 turned an unapproved dependency build script from a warning into a hard error. The blank template declared no build approvals, so the very first command a new user runs failed on any current pnpm:
npx create-objectstack myapp && cd myapp && pnpm install # [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: better-sqlite3@12.11.1, esbuild@0.28.1 # exit 1The scaffold now ships a
pnpm-workspace.yamlapproving the two packages it actually depends on building —better-sqlite3(the native sqlite driver behind@objectstack/driver-sql) andesbuild(compilesobjectstack.config.ts).Both approval keys are present because pnpm reads them by version, and neither alone covers the supported range:
allowBuilds(a package → boolean map) — the only key pnpm 11 honors, and understood back to pnpm 10.31.onlyBuiltDependenciesalone still errors.onlyBuiltDependencies(a list) — pnpm 10.0–10.30, which ignoreallowBuilds.
npm and yarn ignore the file, so the npm install path is unaffected. Both packages ship prebuilt binaries, so this was an install-time hard stop rather than a runtime defect — the project ran fine once installed.
This is the #3091 failure class (in-repo settings masking what users resolve) and was caught by the publish smoke gate added in #3100, which installs the release candidate the way a user does — on whatever pnpm corepack hands a fresh machine.
-
809214f: Stop leaking repo-internal skills into scaffolded projects. The scaffolder (and the docs) advertised
npx skills add objectstack-ai/objectstack --all, and the skills CLI's--allimplies--skill '*'— which includes evenmetadata.internalskills — so repo-internal tooling like.claude/skills/dogfood-verificationlanded in every new project's.agents/skills/. All install commands are now scoped to the published catalog via the/skillssubpath (npx skills add objectstack-ai/objectstack/skills --all), the internal skill is additionally markedmetadata.internal: trueto hide it from interactive discovery, and a template-consistency ratchet plus a scaffold-e2e assertion keep the boundary from regressing.
-
3f218e4: feat(create-objectstack): the blank scaffold ships the three generic connector executors by default
npm create objectstacknow generates anobjectstack.config.tsthat wires therest,openapi, andmcpconnector executor plugins (ADR-0022/0023/0024 + ADR-0097) intoplugins:, alongsiderequires: ['automation']. This closes the last authoring gap in the ADR-0097 promise that integrations are expressible and executable as pure metadata: an author (human or AI) can now add a declarativeconnectors:entry namingprovider: 'rest' | 'openapi' | 'mcp'and have it materialize into a live, dispatchable connector at boot — with no host-code edit.plugins:—new ConnectorRestPlugin(),new ConnectorOpenApiPlugin(),new ConnectorMcpPlugin()(zero-arg = contribute the provider factory only).requires: ['automation']— the automation service performs the materialization and owns the registry the executors register into. It is also a hard dependency of the connector plugins, so a scaffold that lists them inplugins:without it fails boot; automation ships transitively via@objectstack/cli.- deps —
@objectstack/connector-rest,@objectstack/connector-openapi,@objectstack/connector-mcp. - Security (#3055): declarative
mcpstdio transports stay denied by default — opt in per host withnew ConnectorMcpPlugin({ declarativeStdio: ['node'] }).
Brand connectors (Slack, …) remain marketplace/opt-in.
-
83e8f7d: feat(mcp): decouple the stdio auto-start switch from the HTTP surface + surface the MCP endpoint on
os devboot (#3167)The MCP HTTP surface (
/api/v1/mcp) and the long-lived stdio transport used to share one env var:OS_MCP_SERVER_ENABLED=trueturned the HTTP surface on and silently auto-started the stdio transport — which bridges the raw metadata service- data engine with no per-request principal (unscoped). An operator setting it to "make sure MCP is on" got an unscoped transport as a side effect.
@objectstack/types— newresolveMcpStdioAutoStart(). Stdio auto-start is now its own switch,OS_MCP_STDIO_ENABLED(default off);OS_MCP_SERVER_ENABLEDgoverns only the HTTP surface. The legacyOS_MCP_SERVER_ENABLED=truetrigger still starts stdio for one release, flagged as deprecated.=falseis unchanged (it only ever gated HTTP).@objectstack/mcp—MCPServerPlugin.start()gates stdio on the new switch and logs a one-time deprecation warning when started via the legacy alias.@objectstack/cli—os devnow prints the MCP endpoint, the agent-skill URL, and a ready-to-pasteclaude mcp addcommand on boot (gated on the HTTP surface being on), so the "an agent operates the app it's building" loop is discoverable at dev time.create-objectstack— the blank scaffold README documents that the app is itself an MCP server (the serve side), distinct from the consume-side connector.
-
3b6ef8a: Scaffolded projects ship with a
.gitignoreagain —npx create-objectstackproduced none, leavingnode_modules/and.envun-ignored for every new user.npm pack/pnpm packstrip.gitignorefrom a tarball unconditionally, at every depth. The blank template committed one atsrc/templates/blank/.gitignoreand the build faithfully copied it todist/templates/blank/.gitignore, butfiles: ["dist"]publishing dropped it on the way to the registry — so the file was present in the repo, present in every local build, and absent from all 11 files of a real scaffold. Verified against the published 15.1.1 tarball, which shipsdist/templates/blank/.dockerignoreand no.gitignore.The template is now committed as
_gitignore(a name npm does not strip) and restored to.gitignorewhen the template is copied, via aTEMPLATE_FILE_ALIASESmap in the newtemplate-copy.ts. Only.gitignoreis aliased: the strip list is.gitignoreand.npmrc, not "every dotfile" —.dockerignorepacks fine and stays literal.The restored ignore rules also cover
.env/.env.*, which they never did. The template README has users writeOS_AUTH_SECRETandOS_SECRET_KEYinto a.env, anddocker-compose.ymlcalls that file "never committed" — but only the prose said so, and.dockerignorewas the only file that listed it.A packing ratchet in
template-consistency.test.tsguards both halves: it packs the real package, scaffolds from the extracted tarball with the real copy logic, and asserts every template file lands under its intended name. Source-level assertions cannot see this class of bug — the file only vanishes at publish. -
3a8ce9d: fix(create-objectstack): the blank scaffold declares pnpm build approvals, so a fresh
pnpm installno longer exits 1 on pnpm 11pnpm 11 turned an unapproved dependency build script from a warning into a hard error. The blank template declared no build approvals, so the very first command a new user runs failed on any current pnpm:
npx create-objectstack myapp && cd myapp && pnpm install # [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: better-sqlite3@12.11.1, esbuild@0.28.1 # exit 1The scaffold now ships a
pnpm-workspace.yamlapproving the two packages it actually depends on building —better-sqlite3(the native sqlite driver behind@objectstack/driver-sql) andesbuild(compilesobjectstack.config.ts).Both approval keys are present because pnpm reads them by version, and neither alone covers the supported range:
allowBuilds(a package → boolean map) — the only key pnpm 11 honors, and understood back to pnpm 10.31.onlyBuiltDependenciesalone still errors.onlyBuiltDependencies(a list) — pnpm 10.0–10.30, which ignoreallowBuilds.
npm and yarn ignore the file, so the npm install path is unaffected. Both packages ship prebuilt binaries, so this was an install-time hard stop rather than a runtime defect — the project ran fine once installed.
This is the #3091 failure class (in-repo settings masking what users resolve) and was caught by the publish smoke gate added in #3100, which installs the release candidate the way a user does — on whatever pnpm corepack hands a fresh machine.
-
809214f: Stop leaking repo-internal skills into scaffolded projects. The scaffolder (and the docs) advertised
npx skills add objectstack-ai/objectstack --all, and the skills CLI's--allimplies--skill '*'— which includes evenmetadata.internalskills — so repo-internal tooling like.claude/skills/dogfood-verificationlanded in every new project's.agents/skills/. All install commands are now scoped to the published catalog via the/skillssubpath (npx skills add objectstack-ai/objectstack/skills --all), the internal skill is additionally markedmetadata.internal: trueto hide it from interactive discovery, and a template-consistency ratchet plus a scaffold-e2e assertion keep the boundary from regressing.
-
f531a26: feat(protocol): complete ADR-0087 — load-seam handshake, chain backfill 12–15, release artifacts (#2643)
Closes the remaining ADR-0087 gaps (see the ADR's as-built Addendum):
- P0 load seams (D1). The protocol handshake now runs on the boot-time
durable-package rehydration path (
@objectstack/service-packagerefuses an incompatiblesys_packagesrow with the structuredOS_PROTOCOL_INCOMPATIBLEdiagnostic and keeps booting) and onAppPluginfor code-defined stacks (fail-fast before the manifest is decomposed).objectstack lintgainsprotocol/missing-engines-range(warning + fix-it) and thecreate-objectstackblank template stampsengines: { protocol: '^<major>' }(re-stamped at version time byscripts/sync-template-versions.mjs) — the two ends of the grandfathering ratchet. - Chain backfill (D2/D3).
MetadataConversion.retiredFromLoadPathimplements the load-window's second half (retired entries replay only viamigrate meta/ fixture CI). Steps 12–15 land: theapi.requireAuthflip (semantic), the ADR-0090 wave (3 retired conversions + 5 semantic TODOs), theBookAudiencerename (retired conversion), and the ADR-0089 visibility unification (visibleOn/visibility→visibleWhenas LIVE load-window conversions) + the.strict()flip (semantic). The protocol-11compactLayout→highlightFieldsrename is backfilled as a retired step-11 conversion.migrate meta --from 10now reaches protocol 15. - Release artifacts (D4).
spec-changes.jsonis generated from the registries (gen:spec-changes, CI drift-checked), ships in the npm artifact together withapi-surface.json, and is attached to each@objectstack/specGitHub Release withadded[]/removed[]filled from the api-surface diff against the previously published release. The upgrade guide (docs/protocol-upgrade-guide.md) is generated from the same registries and CI drift-checked — a projection that cannot drift.
- P0 load seams (D1). The protocol handshake now runs on the boot-time
durable-package rehydration path (
-
f531a26: Scaffolded projects are now container-ready out of the box: the
blanktemplate ships aDockerfile(two-stage build onto the officialghcr.io/objectstack-ai/objectstackruntime image), adocker-compose.yml(app + Postgres single-host stack), and a.dockerignore, plus a Deploy section in the project README.docker build -t my-app .works immediately afternpm create objectstack.
- eaff014: Scaffolded projects now install the current framework release instead of a stale major. The bundled
blanktemplate had^6.0.0ranges frozen in while the registry was publishing 14.x, sonpm create objectstackproduced a project eight majors behind the docs — and the template's code no longer compiled against 14.x anyway (Field.longTextremoved,api.restno longer adefineStackkey,sharingModelnow required by the ADR-0090 security gate). The template is updated to the current API, and the scaffolder now rewrites every@objectstack/*range in the generatedpackage.jsonto^<its own version>(all packages version in lockstep), so generated projects track the release even if the committed template drifts again. A consistency test ratchets the template's major and the README's template table against the registry. The template README also documents the seeded dev-admin sign-in that data-API curls need.
-
7cf283a: Make
os validatethe author-time verification gate and steer scaffolds toward it.os validatenow runs the same CEL/predicate gate asos build/os compile(ADR-0032): everyvisible/disabled/requiredWhen/validation/flow/sharing predicate is checked for CEL syntax andrecord.<field>existence on the target object. It already ran the protocol schema and widget-binding checks; the expression gate closes the gap so a bare field ref (doneinstead ofrecord.done) — which silently hides an action on every record at runtime (#2183/#2185) — fails validation instead of shipping.os validateis now a read-only superset of the build's checks (no artifact emitted).create-objectstacknow emits anAGENTS.md(and.github/copilot-instructions.md) into every generated project instructing coding agents to runnpm run validateafter editing metadata, aligns the blank template'sdev/startscripts with the example apps (objectstack dev/objectstack start), and sharpens the post-create "Next steps" output.
-
15fc484: Upgrade
@object-ui/*packages to v6.0.@objectstack/cli:@object-ui/consoleand@object-ui/studiofrom^5.4.2→^6.0.0— bundled Studio + Console assets now ship the v6 UI shell (new design language, refreshed sidebar, redesigned record header).@objectstack/account:@object-ui/i18nfrom^5.4.2→^6.0.0— i18n runtime now matches the v6 console/studio API.- Root devDependency
@object-ui/consolefrom^5.4.2→^6.0.0so workspace scripts and the docs build pick up v6. create-objectstack:tarfrom^7.4.3→^7.5.15(security + perf fixes when unpacking remote templates).
Heads-up for consumers:
@object-ui/*v6 is a major release of the bundled UI; pages rendered through the CLI'sstudio/consolemounts may look different from v5. The protocol surface is unchanged.
- 15e0df6: chore: unify all package versions to a single patch release