Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

tooling(pin): commit the pin's packed manifests so a lockfile-only resolution needs no build (objectui#13008) - #13031

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-13008-pin-lockfile-resolvable
Oct 19, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-13008-pin-lockfile-resolvable

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #13008

Option B, measured and built. Every @objectstack/* override now names file:.objectstack-pin/NAME, a committed directory that holds one file per package: its packed package.json at the pin. A lockfile-only resolution, which is what Dependabot runs on a fresh clone, now needs no build. scripts/objectstack-pin.mjs build still builds into node_modules/.cache/objectstack, the path CI caches by the pin and whose stamp turbo hashes. It then hard-links the rest of each packed package beside its committed manifest (git-ignored, with a copy where a link fails). No workflow changes, and no new gate.

Done when, item by item

  1. Measured. On a fresh git clone --depth 1 of this branch's head cd2035a (no node_modules, pnpm 10.31.0), with the command Dependabot's job ran (run 39180936608, read from its log):
    • pnpm update next@16.3.8 --lockfile-only --no-save -r --config.minimum-release-age=0 → exit 0. next moved to 16.3.8, zero @objectstack lines moved, and no node_modules was created.
    • pnpm run setup → exit 0. That clone did its own cold build of the pin.
    • pnpm install --frozen-lockfile → exit 0, then git diff --exit-code pnpm-lock.yaml → exit 0, and git status was clean.
    • Control leg: the same update on a fresh clone of main at 4484a11 → exit 1, ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND ... node_modules/.cache/objectstack/client. That is the job's own failure, reproduced.
    • All four guarantees listed under B are kept: the frozen install, the dependency-name check against the lockfile, the OBJECTSTACK_ROOT stand-in (measured below), and scripts/bump-objectstack.sh writing the manifests with the pin. No guarantee breaks, so A was not built.
  2. The seat's post-landing reading, not mine. It needs the first Dependabot npm run at / on main after this lands to succeed, with its run id recorded. The stand-in for that run is item 1's clean-clone measurement above.
  3. No @objectstack/* resolves from npm. The lockfile has 0 @objectstack/NAME@VERSION keys and 14 @objectstack/NAME@file:.objectstack-pin/ keys. The control pattern (a zod@VERSION key) hits 4, so the count was a real reading. checkOverrideSet is unchanged. The objectui#12957 tests stay green: the whole scripts/ vitest set passes at cd2035a (189 files passed, 2 skipped).

What changed

  • .objectstack-pin/NAME/package.json (7 files) holds the packed manifests at the pin. .gitignore tracks only those files there.
  • eslint.config.js ignores .objectstack-pin, because the spec packs its TypeScript src/.
  • scripts/objectstack-pin.mjs
    • materialize() puts the build beside the committed manifests. canonicalManifest() is the committed form; why it exists is below.
    • build --write-manifests writes the manifests for the pin as it stands.
    • A new check subcommand runs as the root preinstall. status also reports a hollow .objectstack-pin.
    • The override prefix is now file:.objectstack-pin/.
  • scripts/bump-objectstack.sh
    • After writing the pin it runs objectstack-pin.mjs build --write-manifests, with OBJECTSTACK_ROOT unset so the checkout it read does not stand in.
    • It restores the old pin byte for byte when that build fails.
    • It commits .objectstack-sha together with .objectstack-pin.
    • Unchanged: it still refuses before writing when a commit cannot be resolved or read. A bump now needs the network, because it builds.
  • pnpm-lock.yaml: a pure path rename. After sed 's#node_modules/.cache/objectstack/#.objectstack-pin/#', main's lockfile diffs to nothing against this one (exit 0).
  • Spelling the rename made false. These are outside the file surface the claim listed, and the move forced them:
    • scripts/__tests__/check-lockfile-integrity.test.ts builds its samples from the real lockfile by its literal snapshot keys, so it would go red.
    • The key spelling in the headers of scripts/check-lockfile-integrity.mjs and scripts/check-installed-spec-pin-claims.mjs.
    • One comment in scripts/__tests__/ci-cd-pipeline-doc.test.ts.
    • One sentence in content/docs/guide/ci-cd-pipeline.md.

Mechanism hypotheses: readings

  • H1 holds at 4484a11, re-measured: seven overrides at the cache form, setup = build then frozen install, CACHE_DIR, the OBJECTSTACK_ROOT hook, pnpm@10.31.0, and .objectstack-sha = 2c9bf9430a11.
  • H2 holds. A file: directory resolves from its package.json alone (item 1).
    • (a) pnpm hard-links a file: directory's files at install time: the same inode, link count 2, in node_modules/.pnpm. So the build must be in place before the install, which setup already does: build first, then install.
    • (b) The lockfile does not change when the built files appear beside the manifest: byte-identical after a frozen install.
    • Also measured: a lockfile-only run (pnpm update and pnpm install --lockfile-only) does not run the root preinstall. A marker file stayed empty. Its control, a real install, wrote the marker and exited 1.
    • Also measured: a symlinked dist is not followed. pnpm linked only the main file of a symlinked directory, so links have to be per file.
  • H3: the manifest drift choice. A build at the pin refuses loudly when a packed manifest differs from the committed one, and writes nothing. It never overwrites (four axes below).
    • It is measured to fire for real. The first clean-clone setup refused because a second build of the same pin packed @objectstack/client and @objectstack/lint with every value equal and the dependency keys in a different order. The likely cause is pnpm pack rewriting workspace: ranges as they complete.
    • So manifests are committed and compared in canonicalManifest form: dependency maps sorted, nothing else moved, because the order of exports conditions carries meaning. Three independent packs of the pin now agree: this worktree's and two clean clones'.
    • --write-manifests (and so bump-objectstack.sh) writes them.
    • A stand-in writes its own manifests and records in its stamp the ones it replaced. The next build at the pin restores exactly those, and nothing else. Tests pin both halves.
  • H4: both objectstack consumers work unchanged. The override keys, the build subcommand, the OBJECTSTACK_ROOT hook, the objectstack:build script and the virtual-store prefix @objectstack+spec@ that spec-shape-gate's inject step looks for are all kept.
    • Measured, on this branch, with an installed and built objectstack tree at main e11ae4b:
      • OBJECTSTACK_ROOT=TREE node scripts/objectstack-pin.mjs build → exit 0. The 7 manifests show as modified, and the stamp is external with replaced recorded.
      • pnpm install --frozen-lockfile → exit 0, lockfile diff exit 0.
      • node scripts/objectstack-pin.mjs build (back to the pin) → exit 0, and git status is clean again.
      • Then pnpm install --frozen-lockfile → exit 0, lockfile diff exit 0.
    • The literal OBJECTSTACK_ROOT=/home/user/objectstack build → exit 1, ERR_PNPM_CANNOT_RESOLVE_WORKSPACE_PROTOCOL. That checkout is neither installed nor built, which is the precondition the script documents. Nothing was written there (git status --ignored is the same before and after).
  • H5 (objectui#12972). This layout does not make a shared, pin-keyed build harder. materialize() takes its source directory as an argument, so a shared build is one more from. One cost to note: across filesystems the hard link falls back to a copy (the spec is about 184 MB).

Choices I settled (four-axis frame)

  • Refuse, not overwrite, on manifest drift.
    • Business need: Dependabot resolves these exact files, so a stale manifest yields a lockfile that does not match the pin. This is the job's real path.
    • Long term: one writer, --write-manifests, called by the bump.
    • AI mistakes: a hand-edited or forgotten manifest goes red in every CI job's build step instead of being healed in silence.
    • Scope: no new gate. The refusal lives in the build step every installing job already runs.
  • Keep the install loud (preinstall = objectstack-pin.mjs check).
    • Business need: without it, B turns today's loud failure into a successful install of hollow packages. Today a bare pnpm install without the build dies with ENOENT. With B and no check, it would exit 0 and link packages that hold only a manifest, which fail later as module-not-found. Agents run a bare pnpm install in fresh worktrees routinely.
    • Long term and AI mistakes: the check keeps the documented advice true (AGENTS.md: run pnpm run setup), and the refusal names that command.
    • Scope: it is not a CI gate. Every installing workflow already builds first (checked one by one), and a lockfile-only resolution skips it (measured).
    • It is separable. Remove the preinstall line to drop it.
  • Where the manifests live: a new root .objectstack-pin/, not tracked files inside node_modules/.cache/objectstack. In the latter, CI's cache restore would overwrite the committed manifests before the drift check could read them.

Files, line budget, changeset, governance

  • Lines changed outside pnpm-lock.yaml and the generated manifests: +402 −76 = 478, against the card's budget of about 300.
    • The pin script with its tests and the bump script with its tests carry 430 of them.
    • The overrun is the canonical-manifest fix and the install check, both forced by measurements above.
  • Generated data: the manifests are +757 (written by pnpm pack, then canonicalised). The lockfile is +113 −113, all path rename.
  • Changeset: none. node scripts/check-changeset-presence.mjs says none is owed, because no released package's source or contract changed. This matches objectui#13024.
  • Governance: node scripts/check-governed-queue-guard.mjs --test over the 20 changed paths answers NOT GOVERNED.

Acceptance notes (not filed; the seat decides)

  • Stale prose in governed files. These now describe the old directory: AGENTS.md §9's dependency and lockfile section (the override value, and the ENOENT text after removing node_modules), and the comments on the pin-cache steps in .github/workflows/ci.yml, lint.yml, lockfile-dedupe.yml and changeset-release.yml. The cache step itself is still right.
  • Stale comments in objectstack-ai/objectstack. The comments in scripts/build-console.sh and .github/workflows/spec-shape-gate.yml still name file:node_modules/.cache/objectstack/NAME. Their behaviour is unchanged (measured above).
  • scripts/check-lockfile-dedupe.mjs still requires the pin's build stamp before it reads. Under B a dedupe resolution no longer needs the build, so that precondition is now stricter than it has to be. It is not wrong.
  • apps/console/vercel.json. Its installCommand is a bare pnpm install --frozen-lockfile with no pin build. It has failed since objectui#12957 (ENOENT) and still refuses here (in the preinstall). Inferred from reading the file only: no deploy log was read.
  • GitHub's dependency graph may list the 7 committed manifests as npm manifests. This is unmeasured; watch for alerts or update jobs on /.objectstack-pin/NAME after landing.
  • A timeout under load. One full scripts/ run timed out once, in dollar-dialect-alias-census.test.ts (15 s, an ablation case), on a shared box. It passed alone and in the final full run. It is not related to this change.

Generated by Claude Code

…solution needs no build (objectui#13008)

Every @objectstack/* override now names `.objectstack-pin/NAME`, a committed
directory holding only that package's packed package.json at the pin.
`objectstack-pin.mjs build` still builds into node_modules/.cache/objectstack
(CI caches that path) and hard-links the rest of the build beside the
manifests. A build at the pin refuses when a committed manifest is not the
pin's; a stand-in writes its own and the next pin build restores them. The
root preinstall refuses an install with no build beside the manifests.

Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp
Co-authored-by: Claude <noreply@anthropic.com>
…manifests with it (objectui#13008)

The bump runs `objectstack-pin.mjs build --write-manifests` after writing
.objectstack-sha (with OBJECTSTACK_ROOT unset, so the checkout it read
does not stand in), restores the old pin byte for byte when that build
fails, and commits .objectstack-sha with .objectstack-pin. Tests cover
the manifest policy (pin refuses drift, stand-in residue is restored and
nothing else), the install check, and the lockfile key spelling the
rename moved in check-lockfile-integrity's samples.

Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp
Co-authored-by: Claude <noreply@anthropic.com>
…ectui#13008)

Measured on a clean clone: a second build of the same pin packed
@objectstack/client and @objectstack/lint with the same values in a
different dependency-key order (pnpm pack rewrites workspace: ranges as
they complete), and the byte comparison refused it. Manifests are now
committed and compared in a canonical form that sorts the dependency
maps and moves nothing else; an equal committed manifest is left byte
for byte.

Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp
Co-authored-by: Claude <noreply@anthropic.com>
…sts (objectui#13008)

Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 77 chunks) 3167.7 KB 3744.1 KB
Main entry chunk (gzip) 80.8 KB 350 KB
Entry file index-D5T6P5qc.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (buildIdentity.js) 0.80KB 0.49KB
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.41KB 7.88KB
app-shell (runtime-config.js) 24.52KB 8.48KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 614.73KB 149.66KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 247.01KB 68.85KB
fields (index.js) 292.36KB 74.70KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.33KB 11.96KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 55.04KB 16.10KB
plugin-charts (index.js) 85.06KB 23.39KB
plugin-chatbot (index.js) 233.38KB 54.86KB
plugin-dashboard (index.js) 147.53KB 39.95KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.95KB 70.02KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 181.16KB 46.84KB
plugin-gantt (index.js) 178.46KB 45.13KB
plugin-grid (index.js) 271.30KB 75.32KB
plugin-kanban (index.js) 63.37KB 19.51KB
plugin-list (index.js) 140.49KB 36.24KB
plugin-map (index.js) 27.17KB 9.00KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.31KB 11.88KB
plugin-tree (index.js) 15.27KB 5.40KB
plugin-view (index.js) 94.68KB 24.36KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 126.45KB 41.40KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.71KB 2.11KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (product-blocks.js) 0.20KB 0.19KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

ACCEPT · objectui domain:devx seat 1 (#5748) · os-warren · session_01FM29ZqLTrnPNXxijioZwMp · 2026-10-19T08:30Z · head cd2035a259 · card #13008 (claim 6261393554)

  • Form. Draft, base main, author objectstack-fleet[bot], assignee os-warren. The body's first line is Fixes #13008.
  • Path fork. node scripts/check-governed-queue-guard.mjs --test over the PR's 20 changed paths → NOT GOVERNED (6 surfaces checked; none matched). This is an ordinary PR, so the seat lands it through the queue.
  • Option. Option B, as the card asks first. Each @objectstack/* override now reads file:.objectstack-pin/NAME, a committed directory that tracks only the pin's packed package.json. .gitignore keeps everything else under it untracked (/.objectstack-pin/*/*, !/.objectstack-pin/*/package.json).
  • Item 1, the dev's measurement (fresh git clone --depth 1 of cd2035a, no node_modules, pnpm 10.31.0):
    • pnpm update next@16.3.8 --lockfile-only --no-save -r → 0;
    • pnpm run setup → 0;
    • pnpm install --frozen-lockfile → 0;
    • git diff --exit-code pnpm-lock.yaml → 0.
    • Control: the same update on a fresh clone of main 4484a11 → 1 (ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND, client), which reproduces Dependabot job 39180936608.
  • Seat readings on the diff.
    • pnpm-lock.yaml is a pure path rename. main's lockfile at the merge base 4484a11041, with node_modules/.cache/objectstack/ rewritten to .objectstack-pin/, is byte-identical to the head's (diff -q exit 0).
    • .objectstack-pin/spec/package.json is @objectstack/spec 18.0.0-next.3, against .objectstack-sha 2c9bf9430a.
    • CI's restore-then-build path still links a restored build beside the manifests: objectstack-pin.mjs build takes its skip branch and calls materialize (:811). The cache key hashes scripts/objectstack-pin.mjs, so this PR's first CI run builds fresh.
    • bump-objectstack.sh builds the new pin with --write-manifests, commits .objectstack-sha together with .objectstack-pin, and restores the old pin when the build fails.
  • Guarantees the card lists, all kept (the dev's measurements):
    • the frozen install;
    • the lockfile-records-the-manifest's-dependency-names check;
    • the OBJECTSTACK_ROOT stand-in, whose lifecycle was measured round trip against a built clone of objectstack e11ae4b;
    • the bump writing the manifests together with the pin.
    • objectstack's two consumers (scripts/build-console.sh, .github/workflows/spec-shape-gate.yml) read only the override keys, the build subcommand, the OBJECTSTACK_ROOT hook and objectstack:build, and all four are unchanged.
  • Gates, pinned to head cd2035a259. 36 check-runs, all completed: 33 success, 3 skipped. The skips are by design: Test (coverage) and its shards run only on push / schedule (ci.yml:1674, :1905), and dependabot runs only for that actor. The dev's local subset all exits 0, as listed in the report.
  • Accepted deviations.
    • Line budget 478 against ~300 outside the lockfile and the generated manifests. The canonical-manifest comparison and the install check were both forced by measurements.
    • Six paths outside the claim's surface (eslint.config.js, the lockfile-integrity test and header, the installed-pin-claims header, one docs sentence and its test), each made false or red by the rename and named in the PR body.
    • The root preinstall (objectstack-pin.mjs check) is a refusal at install time, not a CI gate. It is analysed on the four axes in the PR body and can be removed in one line. Lockfile-only runs skip it, as measured.
    • One MCP read (get_job_logs).
  • Item 2 (a Dependabot npm run succeeding on main after the merge) is this seat's post-landing reading.
  • Acceptance notes, carried to the landing record:
    • stale override spellings in objectui AGENTS.md §9 and in four workflows' restore-step comments (both governed);
    • the same in objectstack's build-console.sh and spec-shape-gate.yml comments;
    • apps/console/vercel.json's bare install, a read-only inference;
    • check-lockfile-dedupe.mjs stricter than B needs.
    • Noted here; filed after landing where the filing gate admits them.

Landing: ready → auto-merge (squash) → merge queue.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 19, 2026 08:32
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 19, 2026 08:32
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 19, 2026
Merged via the queue into main with commit b014f5e Oct 19, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-13008-pin-lockfile-resolvable branch October 19, 2026 08:49
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

configuration dependencies documentation Improvements or additions to documentation tests

Projects

None yet

2 participants