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 4b248424b6..cd7019fd24 100644 --- a/content/docs/references/automation/state-machine.mdx +++ b/content/docs/references/automation/state-machine.mdx @@ -16,7 +16,7 @@ transition the machine never declared. 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 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"] 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