From d41a1738a9a09c8ccbcf591a63b339e3fb9d53b3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:27:29 +0000 Subject: [PATCH 1/5] =?UTF-8?q?ci(check-links):=20=E6=81=A2=E5=A4=8D=20pul?= =?UTF-8?q?l=5Frequest=20=E6=96=AD=E9=93=BE=E9=97=A8,=E4=BB=85=E6=A3=80?= =?UTF-8?q?=E4=BB=93=E5=86=85=E9=93=BE=E6=8E=A5=20(#6028)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按维护者 2026-08-07 裁决恢复 `Check Links`:重新启用 `pull_request` 触发(branches: main),保留 `workflow_dispatch`,`fail: true` 不变; 方向②(留空壳写注释)已被否决,故被注释掉的 `push:` 残骸按 「declared = enforced」直接删除,而不是留在文件里继续误导读者。 ⛔ advisory-first:不进 required 集,也不加 `merge_group` —— advisory 车道不该消耗合并队列容量。日后若晋级 required,必须按 #6121 的教训在 同一次改动里补上 `merge_group`,否则队列会卡在一个永不上报的必需检查上。 仅检仓内链接的机制选 `--offline`,写在调用处而不是 lychee.toml: 实测 `offline = true` 这个配置键在 lychee 0.19.1 上被静默忽略、在 action 当前钉住的 0.24.2 上才生效,确定性保证不能取决于 action 恰好 装了哪个版本。`^https?://` 这类 exclude 也不够 —— 它要逐个枚举远程 scheme,漏一个就重新引入外网依赖。 门休眠半年期间配置已经漂移到跑不起来,这次一并修好(否则恢复触发器 只会让门以 exit 3 直接死掉,连链接都不检): - `follow_redirects` 在 0.24.2 上根本不是合法键 —— 硬解析错误; - `include_fragments` 已从 bool 变成枚举,裸 `false` 同样硬报错, 改为 `"none"` 保持原语义(不检 fragment)。 root-relative 链接是 content/** 的主力写法,而 lychee 在没有 root-dir 时对它们直接报错。补 `--root-dir /content` + `--fallback-extensions mdx,md` 之后,1286 条此前完全没被检查的 `/docs/*` 链接首次纳入检查面(134 → 1476 条被检)。原来的 `remap` 块一次都没生效过(URI 构造在 remap 之前就失败),已按 declared = enforced 删除,能力由真正生效的 root-dir 承接。 修掉门首次真正运行后暴露的 22 条既有真断链(仅改链接目标,不改散文): - 14 条 ADR 链接归一到仓内既有约定(29 处在用的 GitHub blob URL): `/adr/*` 站点路由并不存在,apps/docs 只有 /[lang]/docs 与 /[lang]/blog 两条路由,也没有任何 /adr 重定向 —— 这些链接在线上 就是 404; - quick-reference 三行指向已退役 schema 的表项删除(audit.zod 按 ADR-0056 移除、registry.zod 按 #4939 退役、graphql.zod 全仓无踪), 并同步修正两处小节计数;connector-auth 有 schema 无页面,改为不带 链接保留信息;plugin-security 页面在 references/kernel 而非 cloud; - README 两处:`@objectstack/account` 已迁到 packages/apps/account; `service-feed` 已在 #1955 删除,整行移除。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3 --- .github/workflows/check-links.yml | 52 ++++++++++--- README.md | 3 +- content/blog/metadata-driven-architecture.mdx | 2 +- content/docs/ai/connect-mcp.mdx | 2 +- .../docs/getting-started/quick-reference.mdx | 11 +-- .../docs/permissions/attachments-access.mdx | 2 +- content/docs/permissions/authorization.mdx | 22 +++--- .../references/automation/state-machine.mdx | 2 +- lychee.toml | 73 +++++++++++-------- 9 files changed, 104 insertions(+), 65 deletions(-) diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index 1a3729d248..57b2ed5c24 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -1,21 +1,33 @@ name: Check Links +# Repo-internal link gate (#6028). +# +# `pull_request` was commented out in a59dc59e (2026-01-28) as a rider on an +# unrelated commit, with no rationale recorded; the gate then sat dormant for +# six months while `lychee.toml` and `fail: true` kept it looking alive. +# Maintainer ruling 2026-08-07: restore the trigger, check REPO-INTERNAL links +# only, and land it advisory-first (NOT in the required set) until it has shown +# a stable green streak. +# +# ⛔ No `merge_group` trigger on purpose: this is an advisory lane, and an +# advisory gate does not get to consume merge-queue capacity. If it is ever +# promoted into the required set, `merge_group` MUST be added in the same +# change or the queue stalls on a required check that never reports (#6121). on: workflow_dispatch: - # push: - # branches: - # - main - # pull_request: - # branches: - # - main + pull_request: + branches: + - main jobs: link-checker: name: Check Documentation Links runs-on: ubuntu-latest + # Least privilege: the job only reads the tree and runs lychee offline. + # There is no issue-filing step, and `--offline` makes zero network + # requests, so neither `issues: write` nor a GITHUB_TOKEN is needed. permissions: contents: read - issues: write steps: - name: Checkout repository @@ -24,14 +36,32 @@ jobs: - name: Check links with lychee uses: lycheeverse/lychee-action@v2 with: - # Use configuration file for path remapping and settings + # `--offline` is the internal-only mechanism, and it lives HERE rather + # than in lychee.toml on purpose: the equivalent `offline = true` + # config key is silently ignored by older lychee (measured: ignored on + # 0.19.1, honoured on the 0.24.2 this action pins). A determinism + # guarantee must not depend on which lychee the action happens to + # install, so it is asserted at the invocation site. + # + # Offline means only `file://` targets are resolved -- every http(s) + # link is reported EXCLUDED, never requested. That is what makes this + # gate deterministic and free of external-network flake. + # + # --root-dir is what makes ROOT-RELATIVE links checkable. Most internal + # links in content/** are site routes (`/docs/permissions`), and lychee + # hard-errors on those unless it is told which directory `/` means. + # The Fumadocs content root is `content/`, so `/docs/x` resolves to + # content/docs/x -- and --fallback-extensions supplies the .mdx/.md + # suffix that a site route omits. Without this pair the gate cannot go + # green at all: 1286 root-relative links fail as "Cannot resolve + # root-relative link ... provide a root dir". args: >- + --offline + --root-dir ${{ github.workspace }}/content + --fallback-extensions mdx,md --config lychee.toml 'content/**/*.md' 'content/**/*.mdx' 'README.md' # Fail the job if broken links are found fail: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - diff --git a/README.md b/README.md index 17df6135bc..38b7470f25 100644 --- a/README.md +++ b/README.md @@ -321,7 +321,6 @@ For the browser, the typed client SDK and React hooks (`useQuery` / `useMutation | [`@objectstack/service-analytics`](packages/services/service-analytics) | Analytics — aggregations, time series, funnels, dashboards | | [`@objectstack/service-automation`](packages/services/service-automation) | Automation engine — flows, triggers, and workflow state machines | | [`@objectstack/service-cache`](packages/services/service-cache) | Cache — in-memory, Redis, multi-tier | -| [`@objectstack/service-feed`](packages/services/service-feed) | Activity feed / chatter | | [`@objectstack/service-i18n`](packages/services/service-i18n) | Internationalization service | | [`@objectstack/service-job`](packages/services/service-job) | Cron & interval job scheduler | | [`@objectstack/service-package`](packages/services/service-package) | Package registry — publish, version, retrieve metadata packages | @@ -343,7 +342,7 @@ For the browser, the typed client SDK and React hooks (`useQuery` / `useMutation | [`@objectstack/cli`](packages/cli) | CLI binary (`os` / `objectstack`) — `init`, `dev`, `start`, `serve`, `compile`, `publish`, `validate`, `generate`, `lint`, `doctor` | | [`create-objectstack`](packages/create-objectstack) | Project scaffolder (`npx create-objectstack`) | | [`@object-ui/console`](https://github.com/objectstack-ai/objectui/tree/main/apps/console) | Fork-ready runtime console SPA (lives in objectstack-ai/objectui, served via `@object-ui/console` on npm) | -| [`@objectstack/account`](apps/account) | Account & identity portal — sign in, organizations, connected apps | +| [`@objectstack/account`](packages/apps/account) | Account & identity portal — sign in, organizations, connected apps | | [`@objectstack/docs`](apps/docs) | Documentation site (Fumadocs + Next.js) | ### Examples diff --git a/content/blog/metadata-driven-architecture.mdx b/content/blog/metadata-driven-architecture.mdx index 150afcb2c8..83223ab567 100644 --- a/content/blog/metadata-driven-architecture.mdx +++ b/content/blog/metadata-driven-architecture.mdx @@ -583,4 +583,4 @@ ObjectStack is our answer. --- -*Want to dive deeper? Explore our [technical specifications](/docs/specifications) or join the discussion on [GitHub](https://github.com/objectstack-ai/spec/issues).* +*Want to dive deeper? Explore our [technical specifications](/docs/references) or join the discussion on [GitHub](https://github.com/objectstack-ai/spec/issues).* diff --git a/content/docs/ai/connect-mcp.mdx b/content/docs/ai/connect-mcp.mdx index 42ef16fa2d..a79af08779 100644 --- a/content/docs/ai/connect-mcp.mdx +++ b/content/docs/ai/connect-mcp.mdx @@ -113,7 +113,7 @@ full-authority local agent, mint the key on a platform-admin or dedicated **service** identity; for a scoped one, mint it on a user with exactly the access it should have. See [environment variables](/docs/deployment/environment-variables#mcp-server) for -the switches and [ADR-0101](/adr/0101-mcp-stdio-principal-admission) for the +the switches and [ADR-0101](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0101-mcp-stdio-principal-admission.md) for the decision. ## What the agent gets diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index f79d95d514..3f9d07fbca 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -76,13 +76,12 @@ Plugin architecture, manifests, and kernel runtime. | **[Metadata Loader](/docs/references/kernel/metadata-loader)** | `metadata-loader.zod.ts` | MetadataLoaderContract | Metadata loading | | **[Package Registry](/docs/references/kernel/package-registry)** | `package-registry.zod.ts` | InstalledPackage, InstallPackageRequest | Package resolution | -## System Protocol (19 schemas) +## System Protocol (18 schemas) Runtime environment, logging, jobs, caching, and observability. | Protocol | Source File | Key Schemas | Purpose | |:---------|:-----------|:------------|:--------| -| **[Audit](/docs/references/system/audit)** | `audit.zod.ts` | AuditEvent, AuditConfig | Audit trail logging | | **[Auth Config](/docs/references/system/auth-config)** | `auth-config.zod.ts` | AuthConfig | Authentication configuration | | **[Cache](/docs/references/system/cache)** | `cache.zod.ts` | CacheConfig | Caching layer | | **[Change Management](/docs/references/system/change-management)** | `change-management.zod.ts` | ChangeRequest, RollbackPlan | Change tracking | @@ -120,7 +119,7 @@ AI/ML capabilities - agents, skills, tools, MCP exposure, RAG, and cost tracking | **[Usage](/docs/references/ai/usage)** | `usage.zod.ts` | AIUsageRecord, TokenUsage | AI usage and cost tracking | | **[Solution Blueprint](/docs/references/ai/solution-blueprint)** | `solution-blueprint.zod.ts` | BlueprintObject, BlueprintApp | Blueprint format for AI app generation | -## API Protocol (19 schemas) +## API Protocol (17 schemas) REST/GraphQL endpoints, real-time subscriptions, and discovery. @@ -130,7 +129,6 @@ REST/GraphQL endpoints, real-time subscriptions, and discovery. | **[Endpoint](/docs/references/api/endpoint)** | `endpoint.zod.ts` | ApiEndpoint, ApiMapping | REST endpoint configuration | | **[Router](/docs/references/api/router)** | `router.zod.ts` | Router, Route | API routing rules | | **[OData](/docs/references/api/odata)** | `odata.zod.ts` | ODataQuery | OData protocol support | -| **[GraphQL](/docs/references/api/graphql)** | `graphql.zod.ts` | GraphQLConfig, FederationEntity | GraphQL API config | | **[Realtime](/docs/references/api/realtime)** | `realtime.zod.ts` | Subscription, RealtimeEvent | WebSocket subscriptions | | **[WebSocket](/docs/references/api/websocket)** | `websocket.zod.ts` | WebSocketConfig | WebSocket protocol | | **[Discovery](/docs/references/api/discovery)** | `discovery.zod.ts` | Discovery, ServiceInfo | API discovery and metadata | @@ -143,7 +141,6 @@ REST/GraphQL endpoints, real-time subscriptions, and discovery. | **[Analytics](/docs/references/api/analytics)** | `analytics.zod.ts` | Analytics | API usage analytics | | **[Documentation](/docs/references/api/documentation)** | `documentation.zod.ts` | Documentation | API docs generation | | **[Metadata](/docs/references/api/metadata)** | `metadata.zod.ts` | Metadata | API metadata endpoints | -| **[Registry](/docs/references/api/registry)** | `registry.zod.ts` | Registry | API registry | | **[Storage](/docs/references/api/storage)** | `storage.zod.ts` | Storage | API storage operations | ## Automation Protocol (5 schemas) @@ -188,7 +185,7 @@ Environments, marketplace, licensing, and multi-tenancy. | **[Environment](/docs/references/cloud/environment)** | `environment.zod.ts` | Environment, EnvironmentType | Deployment environments | | **[Marketplace](/docs/references/cloud/marketplace)** | `marketplace.zod.ts` | MarketplaceListing, PackageSubmission | Plugin marketplace | | **[Plugin Registry](/docs/references/kernel/plugin-registry)** | `plugin-registry.zod.ts` | PluginRegistryEntry, PluginVendor | Plugin registry entries and quality metrics | -| **[Plugin Security](/docs/references/cloud/plugin-security)** | `plugin-security.zod.ts` | PluginSecurityProtocol, SBOM | Plugin security policies | +| **[Plugin Security](/docs/references/kernel/plugin-security)** | `plugin-security.zod.ts` | PluginSecurityProtocol, SBOM | Plugin security policies | | **[Tenant](/docs/references/cloud/tenant)** | `tenant.zod.ts` | Tenant | Multi-tenancy isolation | ## Integration Protocol (1 schema) @@ -214,7 +211,7 @@ Common utilities used across all protocols. | **[HTTP](/docs/references/shared/http)** | `http.zod.ts` | HttpRequest, HttpMethod, CorsConfig | HTTP utilities | | **[Identifiers](/docs/references/shared/identifiers)** | `identifiers.zod.ts` | SystemIdentifier, SnakeCaseIdentifier | Standard ID patterns | | **[Mapping](/docs/references/shared/mapping)** | `mapping.zod.ts` | FieldMapping | Field mapping utilities | -| **[Connector Auth](/docs/references/shared/connector-auth)** | `connector-auth.zod.ts` | ConnectorAuthConfig | Connector auth patterns | +| **Connector Auth** | `connector-auth.zod.ts` | ConnectorAuthConfig | Connector auth patterns | ## QA Protocol (1 schema) diff --git a/content/docs/permissions/attachments-access.mdx b/content/docs/permissions/attachments-access.mdx index c007dac80b..334353bd27 100644 --- a/content/docs/permissions/attachments-access.mdx +++ b/content/docs/permissions/attachments-access.mdx @@ -103,7 +103,7 @@ an auth service is wired, and stamp `owner_id` on the new `sys_file`. Deleting attachments does not immediately delete the underlying bytes (a file can be shared across records). Reclamation is handled by the platform LifecycleService via declarative -[reap guards](/adr/0057-system-data-lifecycle-and-retention): +[reap guards](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0057-system-data-lifecycle-and-retention.md): - **`sys_file`** — when the last `sys_attachment` referencing an attachments-scope file is deleted, the file is tombstoned; a reap guard diff --git a/content/docs/permissions/authorization.mdx b/content/docs/permissions/authorization.mdx index abb452377f..a996d8d8d3 100644 --- a/content/docs/permissions/authorization.mdx +++ b/content/docs/permissions/authorization.mdx @@ -409,16 +409,16 @@ The complete, prioritized gap map lives in issue **#2561** (the production | ADR | Owns | |---|---| -| [0049](/adr/0049-no-unenforced-security-properties) | No unenforced security properties (enforce / mark / remove) | -| [0054](/adr/0054-runtime-proof-for-authorable-surface) | Prove-it-runs — high-risk classes need runtime proofs | -| [0056](/adr/0056-permission-model-landing-verification) | Permission-model landing: OWD, anonymous deny default, D10 matrix | -| [0057](/adr/0057-erp-authorization-core-business-units-and-scope-depth) | Business units, scope depth, declarative RBAC seeding, platform-owned assignment | -| [0066](/adr/0066-unified-authorization-model) | Unified model: capability registry, posture, precedence, future refinements | -| [0068](/adr/0068-unified-user-context-and-built-in-identity-roles) | Built-in identity positions (formerly "identity roles"), `EvalUser` | -| [0069](/adr/0069-enterprise-authentication-hardening) | Enterprise authentication hardening (phased) | -| [0078](/adr/0078-no-silently-inert-metadata) | No inert declarable metadata | -| [0086](/adr/0086-authz-metadata-config-boundary-and-cross-package-composition) | Metadata↔config boundary, package provenance, cross-package composition | +| [0049](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0049-no-unenforced-security-properties.md) | No unenforced security properties (enforce / mark / remove) | +| [0054](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0054-runtime-proof-for-authorable-surface.md) | Prove-it-runs — high-risk classes need runtime proofs | +| [0056](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0056-permission-model-landing-verification.md) | Permission-model landing: OWD, anonymous deny default, D10 matrix | +| [0057](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md) | Business units, scope depth, declarative RBAC seeding, platform-owned assignment | +| [0066](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0066-unified-authorization-model.md) | Unified model: capability registry, posture, precedence, future refinements | +| [0068](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0068-unified-user-context-and-built-in-identity-roles.md) | Built-in identity positions (formerly "identity roles"), `EvalUser` | +| [0069](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0069-enterprise-authentication-hardening.md) | Enterprise authentication hardening (phased) | +| [0078](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0078-no-silently-inert-metadata.md) | No inert declarable metadata | +| [0086](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0086-authz-metadata-config-boundary-and-cross-package-composition.md) | Metadata↔config boundary, package provenance, cross-package composition | | 0090 | Permission Model v2: position rename + vocabulary freeze, profile removal, fail-closed OWD default + external dial, audience anchors, principal taxonomy, publish linter, delegated administration, explain engine + access matrix | | 0091 | Grant lifecycle: validity windows + resolution-time filtering (L1, landed), delegation, break-glass, recertification substrate | -| [0096](/adr/0096-execution-surface-identity-admission) | Execution-surface identity admission — no data-engine call without an explicit principal (the MCP HTTP surface admits identity here) | -| [0101](/adr/0101-mcp-stdio-principal-admission) | MCP stdio principal admission — env-supplied API-key identity, fail-closed, no `system` bypass | +| [0096](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0096-execution-surface-identity-admission.md) | Execution-surface identity admission — no data-engine call without an explicit principal (the MCP HTTP surface admits identity here) | +| [0101](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0101-mcp-stdio-principal-admission.md) | MCP stdio principal admission — env-supplied API-key identity, fail-closed, no `system` bypass | diff --git a/content/docs/references/automation/state-machine.mdx b/content/docs/references/automation/state-machine.mdx index 35cb6a12c9..48509c9d06 100644 --- a/content/docs/references/automation/state-machine.mdx +++ b/content/docs/references/automation/state-machine.mdx @@ -21,7 +21,7 @@ The ledger carried these shapes as `authorable (p)` — provisional, because nobody had checked. Checking matters here more than usual, because -[ADR-0020](../../../docs/adr/0020-state-machine-converge-and-enforce.md) +[ADR-0020](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0020-state-machine-converge-and-enforce.md) **retired this shape as a record-lifecycle declaration**: the top-level diff --git a/lychee.toml b/lychee.toml index 1668350b5d..aa2e296254 100644 --- a/lychee.toml +++ b/lychee.toml @@ -1,38 +1,51 @@ # Lychee Link Checker Configuration -# This configuration enables checking of internal Fumadocs links by remapping -# Next.js routes to actual file system paths +# +# Scope (#6028, maintainer ruling 2026-08-07): this gate checks REPO-INTERNAL +# links only, deterministically, with zero external-network dependency. +# `--offline`, `--root-dir` and `--fallback-extensions` are passed on the +# invocation in .github/workflows/check-links.yml; that file also records why +# `--offline` is asserted there rather than as a config key here. +# +# ⛔ Two keys in this file were HARD parse errors (exit 3 -- the gate dies +# without checking anything) on the lychee the action pins, and stayed +# undetected for the six months the gate was dormant. Do not reintroduce them: +# - `follow_redirects` is not a valid key at all; +# - `include_fragments` is an enum, not a boolean. +# Whenever this file changes, run lychee against it locally before pushing -- +# a config typo here disables the gate rather than failing it loudly. -# Maximum number of concurrent requests +# Maximum number of concurrent requests. +# ⚠️ Inert under `--offline` (no requests are made); kept for a future online lane. max_concurrency = 10 # Accept status codes (2xx = success) +# ⚠️ Inert under `--offline`. accept = [200, 204, 206, 301, 302, 307, 308, 429] # Timeout for requests (in seconds) +# ⚠️ Inert under `--offline`. timeout = 30 # Retry failed requests +# ⚠️ Inert under `--offline`. max_retries = 2 -# Follow redirects -follow_redirects = true - -# Check anchors/fragments in links -include_fragments = false +# Check anchors/fragments in links. +# ⛔ Not a boolean on the pinned lychee (0.24.2) -- it takes one of +# none | anchor-only | text-only | full, and a bare `false` is a HARD parse +# error. "none" preserves the original `false` semantics: fragments unchecked. +include_fragments = "none" # Verbose output # Accepts log level: "error", "warn", "info", "debug", "trace" verbose = "info" -# Remap internal documentation links to actual file paths -# Maps /docs/* routes to content/docs/*.mdx or content/docs/*.md files -# Lychee remap uses simple "pattern" = "replacement" syntax -remap = [ - # Primary remapping for .mdx files - "^/docs/(.*)$ content/docs/$1.mdx", - # Fallback for .md files - "^/docs/(.*)$ content/docs/$1.md" -] +# ⛔ No `remap` block. There used to be one claiming to map `/docs/*` routes onto +# content/docs/*.mdx, and it never fired even once: a root-relative link fails +# URI construction before remapping is reached. Route resolution is now done by +# `--root-dir /content` + `--fallback-extensions mdx,md`, which is +# both the supported mechanism and one that demonstrably runs -- 1286 previously +# unresolvable root-relative links are checked as a result. # Exclude file paths matching these regex patterns exclude_path = [ @@ -41,31 +54,31 @@ exclude_path = [ "(^|.*/)meta\\.cn\\.json$" ] -# Additional exclude patterns for internal paths +# Additional exclude patterns. +# ⚠️ Every entry below is inert under `--offline`, which already excludes all +# non-file schemes; they are kept so a future online lane inherits the intent. +# +# ⛔ Do NOT add a `file://...content/docs...` entry here. The old one +# ("Fumadocs relative links are handled by the framework at runtime") matched +# nothing, which is the only reason the gate had any coverage left -- had it +# matched as written it would have excluded the entire scan surface, i.e. the +# exact links this gate exists to check. exclude = [ # Local development "http://localhost*", "http://127.0.0.1*", - + # Example/placeholder links "https://example.com*", "http://example.com*", - + # Social media (prevents anti-bot false positives) "https://twitter.com*", "https://x.com*", - + # Email links - "mailto:*", - - # Fumadocs relative links (handled by framework at runtime) - # These are links like ./agent that resolve to .mdx files - "file://**/content/docs/**" + "mailto:*" ] # Cache results to speed up subsequent runs cache = true - -# Specify schemes to check (include file:// for local files) -# Leaving this unset allows all schemes including file:// -# scheme = ["https", "http", "file"] From 6c79886bb698ca8ad2bee92a00172115d03695f0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:39:08 +0000 Subject: [PATCH 2/5] =?UTF-8?q?fix(spec):=20ADR-0020=20=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E6=94=B9=E5=88=B0=E6=BA=90=E5=A4=B4=20JSDoc,=E8=80=8C=E4=B8=8D?= =?UTF-8?q?=E6=98=AF=E5=8F=AA=E6=94=B9=E7=94=9F=E6=88=90=E7=89=A9=20(#6028?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `content/docs/references/**` 是 `packages/spec` 的生成物(文件头写着 AUTO-GENERATED — DO NOT EDIT),上一个 commit 只改了生成出来的 state-machine.mdx,下一次 `gen:docs` 就会把它冲掉 —— 由 os-regen pre-commit 钩子当场拦下。 按 contract-first 修在生产者:`packages/spec/src/automation/state-machine.zod.ts` 的 JSDoc 里那条 ADR 链接原本写作 `../../../docs/adr/0020-...md`,从 `packages/spec/src/automation/` 出发解析到 `packages/docs/adr/...` —— 这个 目录并不存在,所以源头本身就是断的,生成物只是忠实地把它复制了出来。 改为仓内既有约定的 GitHub blob URL(全仓 29 处在用),它与文件位置无关, 生成到任何深度的目录都成立。 重新生成后 `content/docs/references/automation/state-machine.mdx` 与本次 生成输出完全一致(regen 后工作树对该文件零 diff),生成物与源头就此对齐。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3 --- packages/spec/src/automation/state-machine.zod.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/spec/src/automation/state-machine.zod.ts b/packages/spec/src/automation/state-machine.zod.ts index f378709c07..b6c46efa0f 100644 --- a/packages/spec/src/automation/state-machine.zod.ts +++ b/packages/spec/src/automation/state-machine.zod.ts @@ -12,7 +12,7 @@ * * The ledger carried these shapes as `authorable (p)` — provisional, because * nobody had checked. Checking matters here more than usual, because - * [ADR-0020](../../../docs/adr/0020-state-machine-converge-and-enforce.md) + * [ADR-0020](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0020-state-machine-converge-and-enforce.md) * **retired this shape as a record-lifecycle declaration**: the top-level * `workflow` metadata type and `object.stateMachines` are both gone, and a * record's legal transitions are declared as a `state_machine` **validation From 34b5d9f7216248453734aa9fcae9755bed52995c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:39:21 +0000 Subject: [PATCH 3/5] =?UTF-8?q?test(check-links):=20=E5=8F=8D=E5=90=91?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E6=8E=A2=E9=92=88=20=E2=80=94=E2=80=94=20?= =?UTF-8?q?=E6=95=85=E6=84=8F=E6=8A=8A=E4=B8=80=E6=9D=A1=E7=9B=B8=E5=AF=B9?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=E6=94=B9=E6=8C=87=E4=B8=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=20(#6028)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 声明 B(咬合面)的红段:预期本次 push 让 Check Links 变红,且报错点名 content/docs/kernel/services-checklist.mdx:188 指向的 ./services-probe-6028-does-not-exist.mdx。下一个 commit 会 git revert 本提交,预期转绿;最终该文件相对 origin/main 净零 diff。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3 --- content/docs/kernel/services-checklist.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/kernel/services-checklist.mdx b/content/docs/kernel/services-checklist.mdx index 9a8e906815..15c8be0ae2 100644 --- a/content/docs/kernel/services-checklist.mdx +++ b/content/docs/kernel/services-checklist.mdx @@ -185,7 +185,7 @@ no message. Below is the former — the fallback case: A service may self-declare that it is not the full thing (ADR-0076 D12), via a `__serviceInfo: { status, handlerReady?, message? }` property on the registered -instance — see [Services → presence is not capability](./services.mdx) for how +instance — see [Services → presence is not capability](./services-probe-6028-does-not-exist.mdx) for how an in-process caller reads it. The two values mean different things, and for the HTTP surface the difference is load-bearing (#4058): From f73b9d1aa88152fa4054870687f3a52d46d2635f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:42:29 +0000 Subject: [PATCH 4/5] =?UTF-8?q?chore(spec):=20=E5=90=88=E5=B9=B6=20main=20?= =?UTF-8?q?=E5=90=8E=E6=8C=89=E6=96=B0=E7=94=9F=E6=88=90=E5=99=A8=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E7=94=9F=E6=88=90=20state-machine=20=E5=8F=82?= =?UTF-8?q?=E8=80=83=E9=A1=B5=20(#6028)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main 带来了 docs 生成器改动(changeset `docs-gen-description-line-layout-and-nested-links`:去掉逐行空行布局、 不再把行内代码路径转成嵌套链接)。该参考页是生成物,两边都改过, 文本合并的结果并不等于生成器的输出 —— os-regen 钩子说的正是这种 「被合并但没有真正重新生成」的状态。 `gen:schema && gen:docs` 后仅此一个文件有 diff(232 个生成文件里的 1 个),内容即新生成器的输出;本单在源头 JSDoc 里改的 ADR-0020 链接 原样保留。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3 --- .../references/automation/state-machine.mdx | 56 ++++--------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/content/docs/references/automation/state-machine.mdx b/content/docs/references/automation/state-machine.mdx index 48509c9d06..cd7019fd24 100644 --- a/content/docs/references/automation/state-machine.mdx +++ b/content/docs/references/automation/state-machine.mdx @@ -8,69 +8,42 @@ description: State Machine protocol schemas @module automation/state-machine XState-inspired State Machine Protocol — hierarchical states, guarded - transitions, entry/exit actions. Used to declare strict business-logic - constraints and lifecycle management, so an AI author cannot "hallucinate" a - transition the machine never declared. ## Where this is authored — the question #4001 had to answer first The ledger carried these shapes as `authorable (p)` — provisional, because - nobody had checked. Checking matters here more than usual, because - [ADR-0020](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0020-state-machine-converge-and-enforce.md) - **retired this shape as a record-lifecycle declaration**: the top-level - `workflow` metadata type and `object.stateMachines` are both gone, and a - record's legal transitions are declared as a `state_machine` **validation - -rule** (`[data/validation.zod.ts](/docs/references/data/validation)`, a flat `\{ from: [to] \}` table — closed - +rule** (`data/validation.zod.ts`, a flat `{ from: [to] }` table — closed since #4001 batch 3b). A schema whose only doors were those two would be - dead surface, and the campaign's own rule is that dead surface gets its - ledger class corrected, not tightened. -One door survives, and it is an authoring door: **`[ai/agent.zod.ts](/docs/references/ai/agent)`'s - +One door survives, and it is an authoring door: **`ai/agent.zod.ts`'s `lifecycle`** is `StateMachineSchema`, and `agent` is a registered metadata - -type — so `defineStack(\{ agents \})`, `POST /api/v1/meta/types/agent` and the - +type — so `defineStack({ agents })`, `POST /api/v1/meta/types/agent` and the Studio agent form all reach this file through `AgentSchema.parse()`. Verified - by parse, not by reading: before this change, ```ts - -AgentSchema.parse(\{ …, lifecycle: \{ - -id: 'probe_machine', initial: 'draft', stats: \{ runs: 3 \}, - -states: \{ draft: \{ onn: \{ APPROVE: 'done' \}, meta: \{ labell: 'Draft', owner: 'ops' \} \}, - -done: \{ type: 'final' \} \}, - -\} \}) - +AgentSchema.parse({ …, lifecycle: { + id: 'probe_machine', initial: 'draft', stats: { runs: 3 }, + states: { draft: { onn: { APPROVE: 'done' }, meta: { labell: 'Draft', owner: 'ops' } }, + done: { type: 'final' } }, +} }) ``` **succeeded**, returning - -`\{ id, initial, states: \{ draft: \{ type: 'atomic', meta: \{\} \}, done: … \} \}` — - +`{ id, initial, states: { draft: { type: 'atomic', meta: {} }, done: … } }` — `stats` gone, `meta`'s two keys gone, and `onn` (one keystroke from `on`) - gone with every transition the author declared. A state machine whose whole - purpose is to *deny* undeclared transitions had silently become one with no - transitions at all, and reported success. So: `authorable`, and every shape below is `strictObject`. @@ -78,23 +51,14 @@ So: `authorable`, and every shape below is `strictObject`. ## `meta` is closed, deliberately XState treats `meta` as an open bag, so leaving it open was the plausible - call and it was checked rather than assumed (the #4909 precedent: a slot - whose openness is real should say `.passthrough()`, not strip). Three facts - say closed here: the hand-written `StateNodeConfig` type beside this - schema declares exactly four `meta` keys, so `passthrough` would open the - Zod while `tsc` stayed shut — a new declared-≠-enforced split; nothing in - this repo reads any `meta` key (`aiInstructions` has no consumer outside - this file's own test); and the current behaviour is not openness but - -*strip* — the probe above shows an author's `meta` arriving as `\{\}`. There - +*strip* — the probe above shows an author's `meta` arriving as `{}`. There is no openness here to preserve, only a silence to end. From 6a25d4816b0a4a3208d3723ca0aa4e8854f91ae2 Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Fri, 7 Aug 2026 13:43:35 +0000 Subject: [PATCH 5/5] =?UTF-8?q?Revert=20"test(check-links):=20=E5=8F=8D?= =?UTF-8?q?=E5=90=91=E9=AA=8C=E8=AF=81=E6=8E=A2=E9=92=88=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E6=95=85=E6=84=8F=E6=8A=8A=E4=B8=80=E6=9D=A1?= =?UTF-8?q?=E7=9B=B8=E5=AF=B9=E9=93=BE=E6=8E=A5=E6=94=B9=E6=8C=87=E4=B8=8D?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E8=B7=AF=E5=BE=84=20(#6028)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 34b5d9f7216248453734aa9fcae9755bed52995c. --- content/docs/kernel/services-checklist.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/kernel/services-checklist.mdx b/content/docs/kernel/services-checklist.mdx index 15c8be0ae2..9a8e906815 100644 --- a/content/docs/kernel/services-checklist.mdx +++ b/content/docs/kernel/services-checklist.mdx @@ -185,7 +185,7 @@ no message. Below is the former — the fallback case: A service may self-declare that it is not the full thing (ADR-0076 D12), via a `__serviceInfo: { status, handlerReady?, message? }` property on the registered -instance — see [Services → presence is not capability](./services-probe-6028-does-not-exist.mdx) for how +instance — see [Services → presence is not capability](./services.mdx) for how an in-process caller reads it. The two values mean different things, and for the HTTP surface the difference is load-bearing (#4058):