Repository navigation
Fix pnpm version mismatch in GitHub Actions workflows - #134
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix action step issue in GitHub workflow
Fix pnpm version mismatch in GitHub Actions workflows
Jan 25, 2026
hotlong
marked this pull request as ready for review
January 25, 2026 02:51
Contributor
There was a problem hiding this comment.
Pull request overview
Aligns GitHub Actions workflows’ pnpm version with the repository’s packageManager setting to prevent CI failures caused by ERR_PNPM_BAD_PM_VERSION.
Changes:
- Updated
pnpm/action-setup@v4versionfrom10.28.0to10.28.1across CI, lint/typecheck, dependency validation, and PR automation workflows. - Ensures workflow pnpm version matches
package.json(pnpm@10.28.1).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| .github/workflows/ci.yml | Updates pnpm version for both test and build jobs to match packageManager. |
| .github/workflows/lint.yml | Updates pnpm version in the typecheck job to match packageManager. |
| .github/workflows/validate-deps.yml | Updates pnpm version in dependency validation workflow to match packageManager. |
| .github/workflows/pr-automation.yml | Updates pnpm version in changeset-check job to match packageManager. |
This was referenced Sep 14, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…Ms (step 1 of ruling A on objectstack-ai#18115) (objectstack-ai#18238) Fixes objectstack-ai#18122 Step ① of three on ruling A of objectstack-ai#18115 (decision batch objectstack-ai#134 item 1): the declared shape the unit-in-key census will read. It adds the two closed duration types and converts nothing — step ② (objectstack-ai#18123) teaches the gate to read them, step ③ (objectstack-ai#18124) converts the rows. > **A** 声明式:引入与 `EpochMs` 同款的闭合时长类型(`DurationMs` / `DurationSeconds`,单位集合由 6 个真时长行决定);体检只认「用了时长类型,或键名带单位」的键;17 个无单位数自然出列,6+2+≥2 个真时长各取类型或改名;名字表退为提示。 ## Where the types landed, and why a sibling file `packages/spec/src/shared/duration.zod.ts`, a new sibling re-exported from `src/shared/index.ts` exactly as `epoch.zod` is — not appended to `epoch.zod.ts`. That file is the INSTANT file: its whole doc block is the instant-vs-duration distinction, and the gate's own source calls its export `INSTANT_ROOT`. Putting durations inside it would make the module's name false about its contents. The barrel re-export is what makes the two files equivalent in published surface, which is the property the card asks to mirror. ## Reachability — measured, not asserted The card's "reachable from the package entry exactly as `EpochMs` is" means the **`./shared` subpath**, not the root entry. Four readings on this branch, the last two taken from the regenerated artifact: ``` $ git grep -n 'DurationMs\|DurationSeconds\|EpochMs' -- packages/spec/src/index.ts (no output; exit 1 — zero hits, so the precedent is absent from the root entry too) $ git grep -n epoch -- packages/spec/src/shared/index.ts packages/spec/src/shared/index.ts:40:export * from './epoch.zod'; $ grep -l 'DurationMs\|DurationSeconds' packages/spec/api-surface/*.json packages/spec/api-surface/shared.json <- the only entry point that carries them $ grep -c 'EpochMs\|DurationMs' packages/spec/api-surface/root.json 0 <- control: the root entry carries neither ``` The `api-surface/shared.json` delta is **4 rows added, 0 removed** — `DurationMs (const)`, `DurationMs (type)`, `DurationSeconds (const)`, `DurationSeconds (type)`. No root `src/index.ts` re-export was added. ## The refinement, and the defaults that chose it `z.number().int().nonnegative()` for both, measured against the six genuine duration rows — the 23 census rows that carry a unit in neither channel, minus the 17 counts wearing a duration's vocabulary: | row | declares today | |---|---| | `kernel/plugin-lifecycle-advanced.zod.ts` `shutdownTimeout` | `z.number().int().min(0).default(30000)` | | `kernel/plugin-security-advanced.zod.ts` `cors.maxAge` | `z.number().int().optional()` | | `system/metrics.zod.ts` `slideInterval` | `z.number().int().positive().optional()` | | `system/auth-config.zod.ts` `session.updateAge` | `z.number().default(60 * 60 * 24)` | | `api/contract.zod.ts` `meta.duration` | `z.number().optional()` | | `data/field-value.zod.ts` `FileValue.duration` | `z.number().optional()` | - **`.int()`** — three of the six already declare it, and both rows that carry a default default to an integer (`30000`, `60 * 60 * 24`). The three bare `z.number()` rows are tightened by adopting it, which is the same tightening `epoch.zod.ts` records for the sites that adopted `EpochMs`. - **`.nonnegative()`** — the weakest floor every declared floor implies. One row declares `.min(0)`, one `.positive()`, none a negative floor. `.positive()` would be too strong in the other direction: `.min(0)` admits `0`, and a zero timeout means "do not wait". - **No third unit.** `DurationMinutes` / `DurationHours` / `DurationDays` are absent on purpose — the unit set is derived from the conversion population, never declared ahead of it. Author state and parsed state coincide (no `.default()`, no `.transform()` on the types themselves), so there is deliberately no `*Parsed` synonym, and the isomorphism is pinned as ADR-0122 requires: `Iso873` / `Iso874` in `type-alias-convention.pin.test.ts` (count 783 -> 785, both prose statements moved with it). `check:spec-parsed-alias` reads that file as its exemption registry, so the pins are load-bearing rather than decorative. ## The pin can actually fail — ablation Dropping `.nonnegative()` from `DurationMs` only, proven on disk before the run (the anchored line count went `1 -> 0`, the mutated spelling `-> 1`) rather than trusted to the editor's exit code: ``` MUTATED: Tests 1 failed | 10 passed (11) × DurationMs refuses a negative literal RESTORED: Tests 11 passed (11) ``` Restore proven by blob identity, not by an exit code: `git hash-object` returned `f02731a63112ffbee7f8263fc2fea36ec0dfc27f`, equal to `HEAD:packages/spec/src/shared/duration.zod.ts`, with `git diff HEAD` empty. ## Two published claims this branch made false, both repaired Neither is scope creep — both are existing published artifacts that my new module invalidated, and both gates went red: - **`packages/spec/llms.txt`** (hand-kept, no generator, ships inside the npm tarball): the `shared` row said 14 schemas against 15 on disk and the section heading said 199 against 200. The row's **prose** was corrected alongside its number — it now names the duration vocabulary beside the epoch instant — because the gate is explicit that rewriting a count without re-reading its row turns a loud staleness into a silent lie. - **`content/docs/getting-started/quick-reference.mdx`**: the `[total]` half of "Shared Protocol (5 of 9 schemas)". Only M moved, to 10. The table is a curated subset and N < M is its normal state, so the duration page is left unlisted exactly as the epoch page beside it already is — the gate names adding a row as a decision rather than a fix. ## Local verification — every command, at `4a04e07eac`, worktree clean | command | exit | verdict | |---|---|---| | `pnpm lint` | 0 | whole repo, `eslint . --no-inline-config` — not a narrowing | | `pnpm --filter @objectstack/spec test` | 0 | `Test Files 478 passed (478)` · `Tests 13622 passed (13622)` | | `pnpm --filter @objectstack/spec typecheck` | 0 | test layer compiles; the new file is not in the debt ledger | | `pnpm --filter @objectstack/spec check:generated` | 0 | `All 15 generated artifacts are up to date` | | `pnpm --filter @objectstack/spec check:duration-unit-keys` | 0 | **the card's acceptance criterion** | | `pnpm check:spec-parsed-alias` | 0 | `1446 bare z.input aliases, 785 pinned isomorphic` | | `pnpm --filter @objectstack/spec check:llms-txt` | 0 | `97 claim(s) re-derived` | | `pnpm check:quick-reference-counts` | 0 | `13 section(s), every heading matches` | | `pnpm check:nul-bytes` | 0 | 8668 files, no raw control bytes | The gate families were derived mechanically rather than guessed — `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, re-derived after each commit until it stopped growing at **109 commands, all 109 run**. 107 green. The two not measured are `check:dual-build-cjs-loads` and `check:type-check-debt`: both refused with `PREREQUISITE NOT MET` because they read every workspace package's `dist/`, which needs `turbo run build --filter='./packages/*' --filter='./packages/*/*'` — a whole-farm build this diff cannot make relevant, since it touches only `packages/spec`. Five further gates that first refused the same way were converted to real green readings by building `@objectstack/formula`, `@objectstack/lint` and `@objectstack/client-react`. **This step changes no gate behaviour, and the gate says so itself**: `check:duration-unit-keys` reports the same 211 duration-shaped keys, 6 `EpochMs` instants and 11 `externalVocabulary` mirrors as on `origin/main`. It recognises exactly one identifier root today — `EpochMs` — so a key typed `DurationMs` is outside its population rather than exempted by it, which is precisely what step ② changes. ## Changeset `minor`, not `patch`: `Clause-②: yes` and the `./shared` entry gains exported symbols. Not `major` — breaking changes ship as `minor` until GA (objectstack-ai#14043), and nothing here is breaking in any case: no key is converted, renamed or refused, and the six rows still declare exactly what they declared before. ## Out of scope, noted not filed - `packages/spec/src/data/driver.zod.ts:414` `idle` sits in the unitless census but is a **pool-stat count** (`{ total, idle, active, waiting }` from `getPoolStats`), not a duration — worth knowing for step ③'s triage, since its name reads like a duration and its neighbours in the census are. Carrier: objectstack-ai#18124, which reads this same census. - `packages/spec/src/shared/connector-auth.zod.ts:38` `tokenExpiry` describes itself as a "Token expiry timestamp" — an **instant**, so `EpochMs` rather than a duration type. Same carrier, objectstack-ai#18124. Neither is a defect, a contract violation or an authoring trap, so neither is filed. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 --- _Generated by [Claude Code](https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Sep 17, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…ionSeconds and two externalVocabulary mirrors (objectstack-ai#18657) Part of objectstack-ai#18124 Clause-②: no Step ③ of ruling A on objectstack-ai#18115 (decision batch objectstack-ai#134 item 1). Eight of the census's ten genuine-duration rows now declare their unit through a channel a reader actually reaches. Two rows are reported back to the director seat instead of converted, each with the measurement that stopped it.⚠️ `Part of`, not a closing keyword, and deliberately: two ruled rows are not delivered here, so a merge must not retire the card. The director seat decides when it closes. The two are named under "Reported back" below. ## The blocker cleared — proved by subject probe, not by card state Card objectstack-ai#18123 (step ②) is open, but its SUBJECT is on `main`. Commit `8ca7aafc45` — *spec(gate): check-duration-unit-keys admits by declaration — retire the name-shape token list*, PR objectstack-ai#18486 — carries `Part of objectstack-ai#18123` rather than a closing keyword, so the behaviour shipped while the card stayed open. On this branch's merge base `922c75588`, `DURATION_ROOTS` in `packages/spec/scripts/check-duration-unit-keys.ts` already maps both `DurationMs` and `DurationSeconds`. That answers the dispatch's open question — *does the gate stop seeing a row that adopts `DurationMs` while its key name carries no unit, or start refusing it?* — with a third answer: **neither**. | | before | after | |:--|:--|:--| | all ten ruled rows in `--list` | absent — each declares nothing, so none is admitted to the census | the eight converted rows are admitted through the channel each adopted | | offenders | 0 | 0 | | declared duration types | 0 | **6** — equals the rows converted by type, which is the card's own acceptance line | | declared `externalVocabulary` mirrors | 11 | **13** | | declared `EpochMs` instants | 15 | 15 | | declared `dimensionless` | 0 | 0 | | census size | 195 | 203 | A converted row is admitted by its TYPE (or by its mirror marker) and exempted from the key-NAME requirement; both contradiction directions stay refusable. Nothing is refused that was not refused before. ## The population, re-derived by key + file, with the unit MEASURED per row The card's "likely unit" column carries its own warning and was not copied. Each row's producer was measured; where the measurement came back EMPTY that is stated as such rather than dressed up. | file · key | measured unit | what was measured, and what it returned | route | |:--|:--|:--|:--| | `api/contract.zod.ts` `BaseResponse.meta.duration` | ms | **No producer exists.** `packages/rest/src` contains no `timestamp:` writer at all, so the envelope's `meta` block is never emitted; a repo-wide sweep of `duration:` assignments in non-test sources returns only `Date.now() - start` sites (`cli/src/utils/config.ts`, `core/src/qa/runner.ts`, `timer.elapsed()` = `Date.now() - start`). Declared from the sibling channel instead: every spec key that spells a processing time spells ms (`tracing.durationMs`, `worker.durationMs`, `worker.avgExecutionMs`), and the schema's own test literal is `150`. | `DurationMs` | | `kernel/plugin-lifecycle-advanced.zod.ts` `HotReloadConfig.shutdownTimeout` | ms | `packages/core/src/hot-reload.ts` is the only in-repo reader and treats it as a millisecond budget; its suite drives `120_000`, `1000` and `50` through it. The objectstack-ai#15676 wave recorded in the migration ledger that this sibling is "deliberately NOT renamed" with `debounceDelayMs`, so the type route is the one the repo already chose. | `DurationMs` | | `kernel/plugin-security-advanced.zod.ts` `KernelSecurityPolicy.cors.maxAge` | seconds | This key IS the CORS `Access-Control-Max-Age` header. Its TWIN — `shared/http.zod.ts` `CorsConfig.maxAge` — is the same field and already declares `.meta({ externalVocabulary: 'CORS ...' })` with the describe "Preflight cache duration in seconds". The live emitter, `packages/adapters/hono/src/index.ts`, reads `OS_CORS_MAX_AGE` with a default of `86400` and hands it to the header. | `externalVocabulary` | | `system/auth-config.zod.ts` `AuthConfig.session.updateAge` | seconds | `packages/plugins/plugin-auth/src/auth-manager.ts` forwards it by name: `updateAge: this.config.session?.updateAge || 60 * 60 * 24` straight into better-auth's `session.updateAge`, seconds. The comment above the pair in the schema already calls BOTH names better-auth's "forwarded by name" — only `expiresIn` carried the marker. | `externalVocabulary` | | `system/metrics.zod.ts` `MetricAggregationConfig.window.slideInterval` | seconds | No runtime reader. The sibling it slides across in the same object literal is `durationSeconds`, and the schema's own fixture pairs `durationSeconds: 300` with `slideInterval: 60`. | `DurationSeconds` | | `system/metrics.zod.ts` `MetricsConfig.retention.downsampling[].resolution` (class B) | seconds | No runtime reader. Its JSDoc says "Resolution in seconds" and its sibling in the same object is `afterSeconds`. The unit was in the JSDoc alone — the objectstack-ai#14519 shape, invisible on the published page. | `DurationSeconds` | | `system/metadata-persistence.zod.ts` `MetadataLoadResult.loadTime` (class D) | ms | **Every** producer agrees: `metadata/src/loaders/filesystem-loader.ts` and `database-loader.ts` write `Date.now() - startTime`; `memory-loader.ts` and `remote-loader.ts` write literal `0`. | `DurationMs` | | `system/metadata-persistence.zod.ts` `MetadataSaveResult.saveTime` (class D) | ms | Same producers, same expression. | `DurationMs` | Class B's second row and one class A-true row are **not** converted — see below. ### Why two rows took `externalVocabulary` and not a type The dispatch left this to measurement. For `cors.maxAge` the decisive reading is that an identical key already exists in this spec and already carries the marker; giving the twin a different declaration would make one field read two ways. For `updateAge` the decisive reading is the schema's own comment, which describes the `expiresIn` / `updateAge` PAIR as better-auth names forwarded verbatim while only one of the two carried the marker. Neither route changes the accepted set, so this does not move the declaration: `Clause-②: no` stands either way. ## Reported back rather than converted — ⛔ both are correct outcomes, not gaps **1. `data/field-value.zod.ts` `FileValue.duration` — the unit is not measurable, and both closed types are hazardous here.** - Producer: **none**. There is no media-metadata writer in this repo — no `ffprobe`, no probe of any kind; `ObjectQLEngine.resolveFileReferences` expands a file reference to `{ id, name, size, mimeType, url }` and never writes `duration`. - Consumer: **none** in this repo, and none in the sibling objectui checkout available here (its only `duration` hits are Tailwind transition classes). - Documentation: `content/docs/protocol/objectql/types.mdx` names `duration` as an optional member and states no unit. - The external convention a producer would follow — `HTMLMediaElement.duration`, `ffprobe` — is **fractional seconds**. `DurationSeconds` is `.int()`, so adopting it would refuse `12.34`; `DurationMs` would contradict that convention by a factor of 1000. - And this key is reachable by STORED data: `FileValueSchema` is the pre-v17 inline blob that ADR-0104's dual-mode window keeps parsing, so `.int()` narrows against rows that may already exist — a different risk class from the rest of this tranche. Three ways forward, all outside this card's fences: accept integral seconds and its truncation; add a fractional-seconds unit to the vocabulary (⛔ "no new type"); or rename (⛔ the rename branch). The director seat sequences it. **2. `kernel/plugin-versioning.zod.ts` `CompatibilityMatrixEntry.estimatedMigrationTime` — the unit is HOURS, which no type in the closed vocabulary covers.** Its JSDoc reads "Estimated migration time in hours" and the schema's own fixtures are `8` and `40`. There is no runtime producer or consumer. `DurationMs` / `DurationSeconds` cannot carry hours, the card fences a new type, and the remaining route — moving the unit into the describe — lands the row straight on `unit-in-prose-not-in-name`, whose prescription is a RENAME of a published authorable key (`kernel/CompatibilityMatrixEntry:estimatedMigrationTime` is on the authorable surface). That owes an ADR-0087 entry in `packages/spec/src/migrations/registry.ts`, a file held by seat 1's in-flight card. ⛔ Not opened. ## Red before green **The gate that reverse-requires this change is `check:docs`** (with `check:skill-refs` beside it). Before regeneration, on the converted sources: ``` ✗ 2 of 15 artifact(s) stale: skill references pnpm --filter @objectstack/spec gen:skill-refs content/docs/references/** pnpm --filter @objectstack/spec gen:docs ``` After `check:generated --fix`: `✓ All 15 generated artifacts are up to date.` **`check:duration-unit-keys` itself cannot go red for an unconverted row**, and that is by design, not an oversight: a key that declares nothing is not admitted to the census, so there is no verdict to fail. What CAN go red is the declaration once it exists — both routes, proved by ablation from the committed state, each leg with an on-disk proof and a hash-verified restore: ``` LEG A mutate slideInterval's describe: seconds -> milliseconds ON-DISK PROOF: deleted-text count 1 -> 0; injected-text count = 1 MUTATED_EXIT=1 [duration-unit-contradicts-schema] .../metrics.zod.ts:416 `slideInterval` — typed `DurationSeconds` (seconds) but the describe says ms. restore proof: disk=2063c360949dc2a35e7fa88501f23ec7cf76970e head=2063c36... RESTORED_EXIT=0 LEG B delete updateAge's externalVocabulary marker ON-DISK PROOF: marker count 1 -> 0 PIN_MUTATED_EXIT=1 (the new pin fails: externalVocabulary marker absent) GATE_WITH_MARKER_REMOVED_EXIT=1 [unit-in-prose-not-in-name] .../auth-config.zod.ts:563 `updateAge` — describe names seconds but the key name carries no unit. restore proof: disk=e1fd6e449e6629f5c848f7ff8bd77d3d5aa6132b head=e1fd6e4... PIN_RESTORED_EXIT=0 (48 passed) ``` Leg B is the stronger reading of the two: it shows the mirror route is *also* load-bearing, which this author expected NOT to be the case. Removing the marker does not make the key vanish from the census, because the describe still names the unit — so the gate refuses it. ## Controls ⭐ **LIT** — already-declared siblings, unchanged and still reading as declared in `--list` after: ``` shared/http.zod.ts:143 maxAge [externalVocabulary: CORS `Access-Control-Max-Age` (WHATWG Fetch)] api/http-cache.zod.ts:82 maxAge [externalVocabulary: HTTP Cache-Control `max-age` ...] metrics.zod.ts:400 durationSeconds [name: seconds] [prose: seconds] (slideInterval's own sibling) auth-config.zod.ts:561 expiresIn [externalVocabulary: better-auth `session.expiresIn`] (updateAge's own sibling) metrics.zod.ts:847 afterSeconds [name: seconds] [prose: seconds] (resolution's own sibling) ``` ⭐ **DARK** — things that must read zero, and do: - `EpochMs` instants 15 → 15 and `dimensionless` 0 → 0: the census's deliberately-excluded classes (the 10 instants, the 17 dimensionless rows) did not move. - The two rows reported back stayed absent from the census: 0 rows outside the ruled population changed declaration. - The census grew by exactly 8 (195 → 203), the number of rows converted, with no other row entering or leaving. - `check:authorable-surface` green with no regeneration: not one authorable key was added, removed or renamed. ## Verification | command | verdict | |:--|:--| | `pnpm --filter @objectstack/spec build` | exit 0 | | `pnpm --filter @objectstack/spec check:generated` | exit 0 — ✓ All 15 generated artifacts are up to date. | | `pnpm --filter @objectstack/spec check:authorable-surface` | exit 0 | | `pnpm --filter @objectstack/spec check:api-surface` | exit 0 | | `pnpm --filter @objectstack/spec check:docs` | exit 0 | | `pnpm --filter @objectstack/spec typecheck` | exit 0 (`tsc --noEmit` + scripts + test layer) | | `pnpm --filter @objectstack/spec test` | exit 0 — 484 files passed, 1 skipped; **13864 tests passed** | | `tsx scripts/check-duration-unit-keys.ts --list` before / after | exit 0 / exit 0 — table above | | `pnpm --filter @objectstack/spec check:duration-unit-keys` | exit 0 — zero offenders, no baseline | | `node scripts/pm/check-widening-tells.mjs --declaration no --diff ...` | exit 0 — 29 files, 6 judged against a declared surface, no widening tell | | `node scripts/check-adr-0087-registration.mjs --base origin/main` | exit 0 — 1 declared-breaking changeset, disposition `not-required (no-migration-prescription)` | | `pnpm check:nul-bytes` | exit 0 | | 20 further derived gates (skill-refs, exported-any, dual-source-exports, export-origins, liveness, empty-state, variant-docs, yaml-examples, strictness-ledger, doc-frontmatter, docs-single-h1, doc-anchors, spec-docblock-symbol-anchors, pm-widening-tells, watch-hint-literal, merge-driver, spec-parsed-alias, docs-spec-enumerations, quick-reference-counts, published-files, type-source-resolution, skills-token-ratchet, changeset-fixed, empty-changeset, changeset-no-major, test-source-alias) | exit 0 | **NOT MEASURED, stated as such:** - `pnpm --filter @objectstack/spec check:skill-examples` — refuses to run without `@objectstack/client-react` built (a prerequisite this branch did not build). Not a red on this diff; CI builds it. - `dispatch-gates.mjs --ran` reconciles **33 of 110** derived families. The remaining 77 are the farm, and are CI's — this is a declared narrowing, not a silent one. - `pnpm check:cross-package-test-inputs` exits 1 on this tree, and **it is not this diff**: the finding is that `packages/cli/test/init-created-files-summary.e2e.test.ts` descends `packages/spec/dist/` with no declared glob reaching inside it. None of this branch's 29 paths touches `packages/cli/**`, the declaration table, or that gate's script — and that script is the subject of a separate in-flight PR. ## Skill-surface readings (this diff touches a published skill path) The only `skills/**` change is one generated line in `skills/objectstack-api/references/_index.md`, written by `gen:skill-refs` because `shared/duration.zod.ts` now has an importer inside the API skill's reachable set. - changed file, before → after: **50 → 51 lines** (+1, generated) - whole published package, sum of every `SKILL.md`, before → after: **6145 → 6145 lines** (+0) - `node scripts/check-skills-token-ratchet.mjs`: exit 0⚠️ **PM 更正(实测推翻本段原文;逐条读数见评论 `5714917300`)。** 原文写「`skills/**` 是受管面,所以本 PR 不走合并队列、等维护者点头」。`node scripts/pm/check-governed-merges.mjs --pr 18657` 在**装了依赖的 checkout** 上读回 `✅ NOT governed — ordinary queue landing applies`:该行被 **objectstack-ai#11705 生成物例外**(维护者 2026-08-25 取 A)按**字节**抬起 —— 它与 `pnpm --filter @objectstack/spec gen:skill-refs` 在本树现算的输出完全相等,且本 PR 的 29 条路径一条都没碰 `packages/spec/scripts/`(objectstack-ai#11084 共编栅栏未触发)。CI 的 `Governed Surface Queue Guard` 同向读 success。⇒ **本 PR 走普通队列落地,⛔ 不等人批。** ## Acceptance notes - `MetricsConfig.retention.downsampling[].resolution` gains its unit in the JSON Schema (`json-schema/system/MetricsConfig.json`) but **not** on the rendered reference page: `build-docs.ts` emits a nested table for an object-valued member (which is why `KernelSecurityPolicy.cors` gains one here) and does not for an ARRAY-of-object member, so `downsampling` stays a one-line type signature. The declaration is correct and reaches the JSON Schema; the page is one generator behaviour short of showing it. Noted, not filed. - `RestServerConfig.responseFormat.includeMetadata` and `PluginRestApiConfig.includeMetadata` both declare "include response metadata (timestamp, requestId)", and nothing in `packages/rest/src` writes that block — the same absence that made `meta.duration` unmeasurable. Declared, unimplemented. Noted, not filed. ## File surface Every path this branch touches: ``` packages/spec/src/api/contract.zod.ts converted row + import packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts converted row + import packages/spec/src/kernel/plugin-security-advanced.zod.ts converted row (mirror) packages/spec/src/system/auth-config.zod.ts converted row (mirror) packages/spec/src/system/metadata-persistence.zod.ts two converted rows + import packages/spec/src/system/metrics.zod.ts two converted rows + import packages/spec/src/api/contract.test.ts pins packages/spec/src/kernel/plugin-lifecycle-advanced.test.ts pins packages/spec/src/kernel/plugin-security-advanced.test.ts pins packages/spec/src/system/auth-config.test.ts pins packages/spec/src/system/metadata-persistence.test.ts pins packages/spec/src/system/metrics.test.ts pins .changeset/18124-genuine-duration-rows-declare-their-unit.md content/docs/references/api/{analytics,auth,automation-api,batch,contract,export,metadata,package-api,protocol,storage}.mdx generated content/docs/references/kernel/{plugin-lifecycle-advanced,plugin-security-advanced}.mdx generated content/docs/references/system/{auth-config,metadata-persistence,metrics}.mdx generated skills/objectstack-api/references/_index.md generated · objectstack-ai#11705 例外实测抬起(NOT governed) ``` From the fenced OPEN set: **nothing**. `packages/spec/scripts/check-duration-unit-keys.ts`, `packages/spec/src/migrations/**`, `packages/spec/src/contracts/automation-service.ts`, `packages/services/service-automation/**`, `packages/types/src/env.ts`, `content/docs/automation/flows.mdx` and `scripts/check-cross-package-test-inputs.mjs` are all untouched. `packages/spec/src/api/contract.zod.ts` IS touched and is the file that is mine; the similarly-named `contracts/automation-service.ts` is not. ## 维护者速读(草稿) **改了什么。** 规格里 8 个「真时长」字段现在把自己的单位写在了合约上:6 个改用闭合类型 `DurationMs` / `DurationSeconds`,2 个挂 `externalVocabulary` 标记(它们的键名来自外部标准,改名会切断与标准的一一对应)。键名一个都没改,也没有增删任何可写的键。 **为什么改。** 一个 `duration: number` 字段,单位只写在代码注释里、或者哪里都没写,作者(很多时候是 AI)照着邻居的数字抄一个过来,就是一次静默的 ×1000 错误——缓存从 1 小时变成 3.6 秒,谁也不会收到报错。这一批把单位放进类型和发布出去的 JSON Schema / 参考页,让读文档的人和写元数据的人看到同一个答案。 **风险与代价(含回滚)。** 三个原本写作 `z.number()` 的字段现在只接受非负整数(`meta.duration`、`loadTime`、`saveTime`)。本仓库里测到的每一个写入方都已经在写整数,所以实测风险为零;真有人写小数,会在写的那一刻响亮报错,而不是悄悄算错。回滚就是 revert 本 PR,没有数据迁移、没有台账条目、没有墓碑。⚠️ ~~本 PR 动到 `skills/**`,属受管面,不走合并队列~~ —— **此句已被实测推翻**(评论 `5714917300`):那一行是生成器自己 `--check` 按字节认证的纯重生成,受 objectstack-ai#11705 例外,**照常走合并队列**。 **席位意见。**(留空) **你要做的。** ① 确认两个「退回」的行由谁排期:`FileValue.duration` 的单位在本仓库测不出来(没有任何生产者),且两个闭合类型都有风险;`estimatedMigrationTime` 的单位是「小时」,现有类型覆盖不到,而改名会动到被别人占用的台账文件。② ~~确认 `skills/**` 那一行生成内容可以随本 PR 一起进~~ —— **这一条不用你答了**:仪器已答(评论 `5714917300`),按 objectstack-ai#11705 例外自动随本 PR 进。⇒ **只剩 ① 需要你的字。** --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CI was failing because
package.jsonspecifiespnpm@10.28.1while all workflows used10.28.0, triggeringERR_PNPM_BAD_PM_VERSIONinpnpm/action-setup@v4.Changes
Updated pnpm version to
10.28.1in:.github/workflows/ci.yml(test and build jobs).github/workflows/lint.yml(typecheck job).github/workflows/validate-deps.yml(validate job).github/workflows/pr-automation.yml(changeset-check job)All workflow files now align with the
packageManagerfield inpackage.json.Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.