Skip to content

docs(services): five published service READMEs document the real plugin entry point, not a .configure() that never existed - #9602

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-9532-service-readme-rewrites
Aug 18, 2026
Merged

docs(services): five published service READMEs document the real plugin entry point, not a .configure() that never existed#9602
os-project-manager merged 2 commits into
mainfrom
claude/issue-9532-service-readme-rewrites

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#9532

Piece 1 of #9532 — the five per-package README rewrites. Piece 2 (the gate) landed as PR #9546 (1c6da6eaf, re-measured on current main) and is what makes this one checkable.

What was wrong

service-analytics, service-automation, service-cache, service-i18n and service-job each told a reader to import a Service…-named class from its own package and call a static .configure({...}) on it. Neither has ever existed. All five READMEs sit in their packages' files arrays with private unset, so they are the pages npm renders.

The real entry point in every case is a kernel plugin constructed with new:

PackageREADME claimedReal entry point
service-analyticsServiceAnalytics.configure()new AnalyticsServicePlugin(options)
service-automationServiceAutomation.configure()new AutomationServicePlugin(options)
service-cacheServiceCache.configure()new CacheServicePlugin(options)
service-i18nServiceI18n.configure()new I18nServicePlugin(options)
service-jobServiceJob.configure()new JobServicePlugin(options)

⚠️ For service-automation the baseline's recorded "real export" is AutomationEngine. That is a real export but not the entry point — AutomationServicePlugin is ("this is the only plugin needed for a fully-functional automation capability", src/index.ts). Taking the baseline's why as a prescription would have produced a second wrong page, which is the trap the cross-lane note on #9532 warned about. Every replacement here is proved forward against the built .d.ts.

Evidence

1. The gate, green, with the baseline shrunk 16 → 10. The six entries these five files own are deleted in the same commit; the baseline is reconciled in both directions, so leaving one would have failed as stale.

✓ check:published-readme-exports — 60 published document(s) across 77 workspace package(s);
185 import statement(s), 48 workspace type entr(ies).
10 known instance(s) still in scripts/published-readme-exports.baseline.json;
2 of the findings are call sites.

2. Three ablations, each predicted before running, each restored afterwards (the working tree is byte-identical to the commit after the last restore — git diff HEAD empty):

#AblationPredictedObserved
Aanalytics README import → the fabricated ServiceAnalytics1 fresh import finding, exit 1exactly that, line 24
Bcache README keeps the genuine CacheServicePlugin but calls .configure(...)1 fresh call-site finding, exit 1⚠️green, exit 0 — see below
B2the same fabricated static, one statement out of the nested position1 fresh call-site finding, exit 1exactly that, line 30
Cre-add one deleted baseline entrystale-entry failure, exit 1exactly that

⚠️Ablation B is a real gate gap, filed separately and NOT fixed here (the dispatch forbids touching the gate). extractMemberCalls' regex requires a boundary character before the receiver and consumes it, so when the preceding match is discarded the next call on the same line loses its boundary:

await kernel.use(CacheServicePlugin.configure({...})); -> matches ["kernel.use"] MISSED
await kernel.use( CacheServicePlugin.configure({...})); -> matches [..., "CacheServicePlugin.configure"] caught

One space changes the verdict. The missed shape — kernel.use(Plugin.configure(...)) — is exactly what a careless rewrite of these five packages produces, since the correct spelling is kernel.use(new Plugin(...)). B2 shows the half is otherwise sound.

3. Set-equality on the parts the gate cannot see. The gate proves every documented name resolves; it cannot prove nothing was omitted or that an option table matches the source. Two rerunnable checks, both measured against the built .d.ts:

  • exports, both directions — 104/104: 32/32 analytics · 50/50 automation · 6/6 cache · 4/4 i18n · 12/12 job. Zero undocumented, zero fabricated.
  • options — 45/45 declared properties documented across 10 interfaces (AnalyticsServicePluginOptions 8/8, AutomationServicePluginOptions 8/8, CacheServicePluginOptions 4/4, MemoryCacheAdapterOptions 3/3, I18nServicePluginOptions 5/5, FileI18nAdapterOptions 3/3, JobServicePluginOptions 4/4, IntervalJobAdapterOptions 2/2, CronJobAdapterOptions 6/6, DbJobAdapterOptions 2/2), and every token in a README option table exists as a declared name in that package's own src/ — 0 fabrications in the reverse direction.
  • REST surfaces, set-equal to the ledgers: analytics 4/4 (3 from runtime/src/domains/analytics.ts + POST /api/v1/analytics/dataset/query from the REST ledger), automation 15/15 against the runtime route ledger's /automation domain, i18n 3/3 against I18N_ROUTE_LEDGER. cache and job: 0 routes in either ledger, matching their "No HTTP surface" sections.

The nine fabricated analytics endpoints, the five fabricated automation endpoints and POST /api/v1/i18n/translate are gone.

Two capability claims corrected, not deleted

The card asked whether any of the five carried a compliance-style claim on top of the fabricated API, as plugin-audit's did. Measured: none of the five mentions SOC 2, HIPAA, GDPR, PCI, ISO 27001, CCPA, FedRAMP, "compliance", "compliant" or "certif…". That shape is absent here. Two capability over-claims are, however, present, and the source decides both:

  • service-cache advertised Redis as production support.RedisCacheAdapter throws RedisCacheAdapter not yet implemented from every method, and new CacheServicePlugin({ adapter: 'redis' }) throws during init rather than falling back to memory. The README now says so in a callout at the top and points at registering a custom ICacheService under the slot.
  • service-job's adapter: 'interval' stores cron registrations that never fire. Now stated in the adapter table rather than left to be discovered.

Also corrected against the source

  • ICacheService has six members; fourteen documented methods (mget/mset/del/delPattern/namespace/ttl/expire/persist/incr/incrby/decr/getOrSet/invalidateTag/resetStats) do not exist. set(key, value, { ttl }) corrected to the real positional set(key, value, ttl?) in seconds; CacheStats.keys/hitRate corrected to keyCount with no hitRate.
  • II18nService.t is synchronous and takes the locale positionally — t(key, locale, params?). The namespace/plural/context/returnObjects/formatter dialect and the {{lng}}/{{ns}} file layout were fabrications; the adapter reads one {locale}.json per locale with {{param}} interpolation only.
  • IJobService.schedule is positional — schedule(name, schedule, handler, options?) — and resolves void. Retry defaults corrected to the enforced ones (maxRetries: 0, backoffMultiplier: 1, backoffMs: 1000, maxRetryDelayMs: 30000, jitter: false).
  • IAutomationService is execute(flowName, context?) + listFlows() plus optional members; AutomationResult's machine-readable field is code, not errorCode.
  • The useTranslation hook the i18n README imported from @objectstack/client-react exists nowhere in this repo. That package ships useObjectStackLocale, which returns the active locale string — it is not a translator — so the client section is rewritten around ObjectStackProvider plus client.i18n.getLocales / getTranslations / getFieldLabels.

Changeset

⭐ A patch changeset for all five packages, and the bump is the point rather than a formality: these READMEs reach a reader only through npm, and a docs-only fix with no version bump never gets published at all.

Gates

Union derived by node scripts/pm/dispatch-gates.mjs from the paths in git diff --name-only $(git merge-base origin/main HEAD) and run at a5c5e3cff — the final commit — all exit 0:

check:published-readme-exports · check:nul-bytes · check:changeset-gate-self-tests · check:objectui-changeset · check:test-source-alias · check:type-source-resolution · check-adr-0087-registration · check-changeset-no-major · check-empty-changeset · docs-audit/check-affected-docs

The union needs a built tree (the gate hard-errors on a missing type entry rather than skipping); the full workspace build ran first — 71 tasks, all successful.

⛔ Not touched: scripts/check-published-readme-exports.mjs (gate gap filed instead), and plugin-audit's README — it is the gate's negative control and still passes.


Generated by Claude Code

… not a `.configure()` that never existed (#9532)
`service-analytics`, `service-automation`, `service-cache`, `service-i18n` and
`service-job` each told a reader to import a `Service…` class from its own package
and call a static `.configure({...})` on it. Neither has ever existed: no class in
this repo exposes a static `configure`, and none of those five identifiers is
exported by anything. All five READMEs are in their packages' `files` arrays with
`private` unset, so they are the pages npm renders.
Each README is rewritten against the package's BUILT type surface — the entry point
is a kernel plugin constructed with `new` in every case — and each package's entry is
deleted from `scripts/published-readme-exports.baseline.json` in the same commit
(that baseline is reconciled in both directions, so a stale entry fails too):
16 entries -> 10.
A name swap alone would not have gone green, which is the point of the gate landed in
#9546: substituting the genuine class while keeping `.configure(...)` turns the import
finding into a call-site finding rather than into silence.
Also removed as fabricated: nine analytics REST endpoints of which none exists, a
five-endpoint automation REST list matching no mounted route, fourteen `ICacheService`
methods on a six-member contract, an i18n dialect with namespaces/plurals/formatters
over a synchronous `t(key, locale, params?)`, and ten `IJobService` methods on a
three-required-member contract.
Two capability claims are corrected rather than deleted, because the source decides:
`RedisCacheAdapter` throws from every method and `adapter: 'redis'` throws at init, and
`JobServicePlugin`'s `adapter: 'interval'` stores cron registrations that never fire.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
…hed surface (#9532)
The value exports were listed but the 30 type exports were not, so the README's
export list was a subset rather than the surface. A rerunnable set-equality check
over the built `.d.ts` now reports 50/50 for this package (104/104 across all five),
in BOTH directions — the gate proves every documented name resolves, it cannot prove
nothing was omitted.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 5 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 5 changed file(s) yielded no anchor (packages/services/service-analytics/README.md, packages/services/service-automation/README.md, packages/services/service-cache/README.md, …) — pages documenting those are invisible to this run

Coarse fallback — 12 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ca2e020e47bbe1121aa7e12aaeeae0f6d402ce5fpackageMentionDocs.

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

The two findings the body refers to are filed (both unassigned, both land in this repo, neither touched here):

#9611 explains why it was not absorbed here under the bounded in-place exemption: this diff's derived gate union carries no package test or typecheck job, and editing two src/*.ts files would add that surface.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32141617980 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (3/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/error-envelope.conformance.test.ts�[2m > �[22m#8087 — every code the dispatcher door can emit is parsed against ApiErrorSchema�[2m > �[22mevery pending code is still unre
    

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 8 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Triage of the queue failure — ⛔ do not re-queue. Branch 3: a semantic conflict with a sibling PR in the same group, not this PR and not flaky.

PM dispatch seat, session session_01Y26DJEHSBhhAQ6wwfsHNza. Working the bot's checklist rather than reaching for the re-queue button, since it warns that each blind re-queue rebuilds every PR behind it.

1 — is the failing test in a package this PR changed? No, structurally.

Failing: packages/runtime/src/error-envelope.conformance.test.ts:320"every pending code is still unregistered — the row comes out when #8846 lands".

This PR's entire diff: five packages/services/*/README.md, one changeset, and scripts/published-readme-exports.baseline.json. ⇒ Nothing in packages/runtime. A markdown diff cannot move a runtime conformance assertion.

2 — flaky? No. The ratchet is firing exactly as designed.

The test's own comment states its contract:

The ratchet's test-side half. When the spec lane registers one of these, this goes red and the stale row must be deleted rather than left promising work already done.

And PENDING_LEDGER_REGISTRATION is derived, not hand-maintained — UNREGISTERED_CODE_SITES.filter(verdict === 'pending-registration'), whose doc says: "This list is the OUTPUT of a measurement, not a wish… It shrinks only by being registered."

3 — ⭐ the actual cause: main is self-consistent; the group is not

Measured on origin/main:

factvalue
FLOW_CONVERSION_CONFLICT registered in the ledger?no
still a pending-registration row in dispatcher-error-vocabulary.ts:365?yes
⇒ ratchet on main aloneconsistent, green

main is fine and this PR is fine. The red comes from a sibling in the same queue group: PR #9582 (#9567, spec lane) admits FLOW_CONVERSION_CONFLICT to the ledger — the spec seat's ledger records "half 1 (FLOW_CONVERSION_CONFLICT ledger admission, 292→293) ACCEPTED". Registration plus the still-present pending row is precisely the state the ratchet is built to reject.

⚠️This will red every queue group PR #9582 is placed in, not just this one — consistent with the bot's "8 failed queue builds in the last 24h". ⇒ Re-queueing #9602 alone accomplishes nothing except a full rebuild behind it, unless the grouping happens to differ.

What actually fixes it — ⛔ not mine to do

The remedy is the one the test names: the registering PR must delete the stale UNREGISTERED_CODE_SITES row in the same change. That is #9582's to carry, in the domain:spec lane (seat: os-steve, per #6017). I am flagging it there rather than touching another seat's PR.

Action for this PR: none. It waits for #9582 to land complete, or to leave the queue. ⛔ No push, no re-queue from this seat.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xltooling

Projects

None yet

2 participants

@os-project-manager@claude