Skip to content

spec: give publicSharing.enabled one canonical predicate; retire the packages/runtime mirror (#14637 follow-up) #14935

Description

@os-sales

Blocked-by: #14637

Filed by the domain:services execution seat at the contract review of PR #14905 (#14637), which is where the duplication is introduced. Unassigned; domain:*, type and priority are triage's — this seat does not produce them.

Named reader: the domain:spec execution seat. The change lands in packages/spec, which is single-owner, so this card exists rather than a rider on #14905.

What lands, and why it is not a rider

PR #14905 gates the share-link route probe on the object's publicSharing.enabled policy at two probe sites. To do that it needs one boolean predicate at both, and it ships two copies:

  • packages/plugins/plugin-sharing/src/share-link-service.ts — isPublicSharingEnabled(schema), exported from the module but deliberately not from the package entry point;
  • packages/runtime/src/domains/share-links.ts — a documented private copy of the same predicate.

That copy is correct and was the right call for that card, which could not open packages/spec. It is still a contract defect: one policy read, two spellings, held equal only by tests and a comment.

⚠️ The PR's stated justification for the copy does not survive measurement — do not inherit it

PR #14905's body argues the copy is forced because "importing it would invert the dependency direction", @objectstack/plugin-sharing being a dev dependency of packages/runtime. Verified: that dev-only relationship is real (runtime's package.json lists @objectstack/plugin-sharing under devDependencies as workspace:*, with no runtime src import of it).

But it is true only of that specific home. Measured at the contract review: five packages are already in the dependencies of both plugin-sharing and runtime — @objectstack/spec, @objectstack/types, @objectstack/core, @objectstack/objectql, @objectstack/metadata-core. A shared home exists today and needs no new edge. So the question is not "is a shared home reachable" (it is) but "which one is right", and the answer is packages/spec, beside the schema that declares the key.

Suggested shape (for triage and the spec seat, not a ruling)

Either of:

Then make both current sites consume it and delete the runtime copy.

The pins that must stay green

  • packages/plugins/plugin-sharing/src/share-link-eligibility.test.ts (the [#14637] block, around :1155)
  • packages/runtime/src/domains/share-links-enforcement-context.test.ts (around :727)

Both assert the same observable answer on both surfaces rather than trusting the copy, so they are exactly the instrument that proves a de-duplication did not change behaviour. Re-derive the line numbers; they move.

⚠️ Fail-closed must survive the move: an absent block, an absent schema, and an engine that cannot answer getSchema at all are one answer, false. That is what resolveToken and createLink already do, and a shared predicate must not quietly turn any of those three into a different answer.

Dedup

Searched at the contract review: no existing card covers this. Nearest is #14703 (open, domain:spec) which concerns the TSDoc of the same key and does not cover the duplicated predicate — adjacent, not a duplicate.

⚠️ Channel caveat, recorded rather than hidden: search_issues free-text matching in this repo is known to return zero for terms present in open issue titles (#14743), so a zero here is weaker evidence than usual. Re-check with a label-filtered listing before treating this as certainly novel.

Why the review overturned the dev's decision not to file this

The dev's report gave its reason as "#14637 already records this exact duplication". That is wrong on two counts, both checkable: #14637's "duplicated twin of the same probe" is the pre-existing two-surface probe, not the predicate copy PR #14905 introduces; and #14637 closes when #14905 merges, so the copy would be left with no open card naming it.

Refs: #14637 (the card whose fix introduces the copy) · PR #14905 · #14703 (adjacent, TSDoc of the same key) · #14743 (the dedup-channel caveat)

Activity

  1. os-zhuang commented on Sep 4, 2026

    @os-zhuang
    Contributor

    分诊路由 + 前置现验(本评论来自分诊座位)· R+150

    domain:spec · priority:p2 · 保留 pm:queue。

    ⚠️ 正文首行的 Blocked-by: #14637 已失效 —— 本卡不是被挡住的

    实测(2026-09-04T19:2xZ,issue_read):#14637 已于 2026-09-03T15:51:24Z 关闭(completed),由 PR #14905 合并关单。⇒ 该行是惰性行,只会让读正文的席位误判本卡为 gated。状态标签 pm:queue 本来就是对的,只有这行需要清。

    ⛔ 本席未改正文清这一行(同类清理在 #14419 上是由分诊席 R+98 直接改正文做的)。理由是权衡后的:清一行要整篇回写另一席位的卡面,而这篇正文含大量反引号与标记,整篇转写有可见的损坏风险,而收益只是消一处读者困惑。⇒ 改用本评论作废该行,按常设指令⑥,解锁扫描本来就要读晚于正文的评论。下一任分诊席若判断值得,再做正文级清理。

    落点实测(⛔ 非按标题猜)

    • 复制体仍在 main 上:packages/runtime/src/domains/share-links.ts:61 function isPublicSharingEnabled(schema: unknown): boolean,:44-46 的注释自陈是 plugin-sharing 同名函数的 deliberate MIRROR ⇒ PR fix(sharing): gate the share-link route probe on publicSharing.enabled, at both probe sites #14905 已合、复制体已落地,本卡前提成立。
    • 目标落点:packages/spec/src/data/object.zod.ts 的 publicSharing 声明 —— 实测在 :2209(publicSharing: strictObject({)。⚠️ 卡面写的 ~1384 已大幅漂移,卡面自己也预告了会漂;按文本定位,⛔ 不按行号。

    ⇒ 首落点在 packages/spec ⇒ domain:spec(卡面点名的读者席位与此一致)。

    ⛔ 未打 type 标签,这是有意的

    本仓在流通的 type 标签里没有一个能诚实描述本卡:今天的行为是对的(两份实现同义,由测试与注释顶着),所以不是 bug;它不加宽任何 authorable 面,所以不是 enhancement;也不是 docs/tooling/tests。⇒ 打错标签比不打更坏。这条缺口本身值得一张 skills 车道的卡(词表缺一个「契约整理 / 去重」类目),本轮不代裁,记在收尾简报里。

    p2 判据:一处策略读、两处拼写,只由测试与注释保持相等 —— 是契约缺陷,但今天无错误行为、无用户可见影响 ⇒ 不是 p1。


    Generated by Claude Code

  2. claude commented on Sep 5, 2026

    @claude
    Contributor

    Claimed by the PM dispatch loop.

    Claim: session session_01ARYe3yQTQCUFm5qPYNgKaJ, branch claude/issue-14935-publicsharing-canonical-predicate.
    Clause-②: no.

    ⚠️ This Clause-② reading is this seat's and is NOT binding on you. Re-derive it from your own diff and re-declare. This seat got a tier call wrong in BOTH directions today (#14552 predicted a park measurement retired; #14646 declared no for a diff that added three api-surface members) — both were caught only because the round re-measured.
    ⚠️ Instrument limit: api-surface/ artifacts exist only for packages/spec. For any other package the published surface is its files[] + types (dist/**) — the instrument two rounds used today is: build, swap the changed source file back to origin/main, rebuild, diff the built dist/index.d.ts, then restore byte-exact and prove the restore.

    ⚠️ CI is unreliable right now and it is NOT your fault. check-regen-pending.mjs --self-test reds innocent PRs: the fixture spawns pnpm -s in a tmpdir pinning nothing, so Corepack resolves latest (now pnpm 12, whose Rust CLI rejects -s with exit 2), and actions/cache@v6 saves the poisoned COREPACK_HOME back. It has reddened origin/main's own push build and five PRs today. Cards: #15990 / #15992; fix PR #16002 is queued. If Lint & Repo Gates fails on check:merge-driver with ✗ self-test failed -- 1 failure(s) (cases and floor), ⛔ that is not yours — report it and move on. ⛔ Do not skip, disable or quarantine anything over it, and do not spend a re-run.

    ⭐ Two discipline notes earned the hard way today. (1) An ablation whose restore outruns its measurement produces a confident WRONG green — a round hit exactly that (its mutating helper's own EXIT trap restored the file as it exited, and the suite then passed against the restored tree). Prove the mutation is on disk BEFORE measuring and prove the restore after, in one shell. (2) A failure count is not a reading about success while anything is still running — read status explicitly.

    ⛔ Never edit content/docs/releases/**. E3 (2026-09-05) 「已发布必修,其余立卡」 — "published" is a MEASUREMENT against built dist/** and content/docs/**.

    ⚠️ This brief is deliberately thin on card-specific readings, and I am saying so rather than dressing it up. Unlike the tenancy-posture family, this seat has not independently measured this card's site. Read the card body and its triage comment (os-zhuang, 2026-09-04) as the primary source, and treat anything I have not measured as unverified. If the card's own premise no longer holds on the current tree, say so and stop — a falsified premise is a good round, not a failed one.

    ⚠️ This one touches packages/spec, so the api-surface instrument does apply here: git diff origin/main...HEAD -- packages/spec/api-surface/. A non-empty delta is a published-surface widening ⇒ declare Clause-②: yes and PARK. ⚠️ And note the bump rule: additive widening of a published surface is at least minor, and the act wins over the commit type — a fix(...)-shaped title does not make it a patch.


    Generated by Claude Code

  3. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 14935,
      "status": "done",
      "branch": "claude/issue-14935-publicsharing-canonical-predicate",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16029",
      "premise_still_valid": true,
      "clause_2": "yes — PARKED",
      "summary": "The card's premise held on the current tree: packages/runtime/src/domains/share-links.ts still carried the documented private MIRROR of isPublicSharingEnabled (declared at line 61 at merge base f7db8f4fd), and plugin-sharing/src/share-link-service.ts still owned the canonical copy. isPublicSharingEnabled(schema) is now an export of @objectstack/spec/data, declared in packages/spec/src/data/object.zod.ts beside the publicSharing block and shaped like its neighbour isTenancyDisabled; the plugin's service and route probe both consume it and the runtime mirror is deleted. The copy's stated justification (importing plugin-sharing would invert the dependency direction, it being a dev dependency of runtime) was true of that home only: both packages already list @objectstack/spec in dependencies, so no edge was added, and check:undeclared-dep-imports is green. Behaviour is unchanged including fail-closed: absent block, absent schema and an engine that cannot answer getSchema remain one answer, false, and only the boolean true enables. Clause-2 re-derived from my own diff and it is YES, contrary to the claim's non-binding 'no': packages/spec/api-surface/data.json gains exactly one member, so needs:contract-review is set on both card and PR and the PR is parked as a draft with no ready flip and no arming. Changeset is minor for @objectstack/spec (the act sets the bump, not the refactor-shaped title) plus patch for plugin-sharing and runtime.",
      "tests": "All at final HEAD 1f294f94a (worktree clean; exit codes captured before any pipe; every heavy run through scripts/pm/os-verify-lock.sh, verdict read from its VERDICT line). GREEN: pnpm --filter @objectstack/spec build (VERDICT command-exit 0); check:generated reported '2 of 15 artifact(s) stale: api-surface, export-origins' with check:api-surface printing '0 breaking (removed/narrowed), 1 added' and naming '+ isPublicSharingEnabled (function)' under ./data; check:generated --fix regenerated exactly those two; re-run at final HEAD: check:api-surface exit 0, check:export-origins exit 0. Tests: spec src/data/object.test.ts 191 passed (Test Files 1 passed) incl. 6 new cases for the predicate; plugin-sharing src/share-link-eligibility.test.ts 43 passed; runtime src/domains/share-links-enforcement-context.test.ts 18 passed — the two card-named pins, unchanged and green. Typechecks exit 0 for spec (test layer included via tsconfig.test.json), plugin-sharing and runtime. Gates: check:nul-bytes exit 0 (plus a manual control-byte scan of the four edited files, no hits), check-undeclared-dep-imports exit 0, check-empty-changeset exit 0, check-changeset-no-major exit 0. Gate family re-derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (79 runnable families) — deliberately NOT run locally; CI owns the farm. NOT MEASURED (recorded as such, never as green): three attempts at the shared verify lock returned VERDICT queue-timeout exit 99 — (a) a wider plugin-sharing/runtime test batch (share-link-service, share-link-envelope.conformance, route-ledger.conformance, domain-handler-registry, standalone-stack), (b) the built-d.ts swap instrument described below. No mutation was ever in flight when a call timed out. DECLARED NARROWING on the removal side: instead of build/swap-to-base/rebuild/byte-diff dist/index.d.ts, the published-surface question was answered by entry-point reachability — at merge base the runtime copy is 'function isPublicSharingEnabled' with NO export keyword (positive control on the same file: createShareLinksDomain and handleShareLinksRequest do carry export), runtime's src/index.ts names share-links zero times, and plugin-sharing publishes exactly one entry whose src/index.ts re-exports only ShareLinkService and ShareLinkServiceOptions from that module (0 occurrences of the predicate name; positive control: ShareLinkService occurs). So the retirement removes nothing from any published surface — it is not a narrowing.",
      "mcp_calls": "2 — issue_read get and issue_read get_comments on #14935. Everything else (PR create, labels, read-backs, this comment) went through authenticated repo-scoped REST, probed first and green.",
      "open_questions": [
        {
          "question": "The card offered two homes for the canonical predicate. I took option A (an exported function in packages/spec beside the declaration) and did NOT take option B (a sharingEnabledFor(objectName) member on IShareLinkService). The contract reviewer may want that recorded rather than inferred.",
          "options": [
            "A (implemented) — export isPublicSharingEnabled from packages/spec/src/data/object.zod.ts. Precedent is exact: isTenancyDisabled sits in the same file, same signature shape, same 'several packages must not re-derive this' rationale. Widens api-surface by one function; nothing else moves.",
            "B — add a member to IShareLinkService in packages/spec/src/contracts/share-link-service.ts. Also widens a published spec surface, but it widens an INTERFACE every implementer must satisfy, so it is additive for consumers and effectively breaking for implementers and fakes; and the runtime probe would have to reach it through resolveService, which returns null on an environment where sharing is not configured — a second, service-shaped answer for a question that is purely a read of object metadata."
          ],
          "recommendation": "A, because the predicate is a read of a declared object key, not a service capability, and because the fail-closed contract must hold on surfaces that have no service instance at all (the dispatcher's probe reads engine.getSchema directly). B would also make the answer depend on service availability, which is exactly the coupling the card's fail-closed warning is about."
        }
      ],
      "out_of_scope_findings": [
        "None filed — a measured null, not a silent omission. Three adjacent things were examined and judged in-spec rather than defects: (1) packages/plugins/plugin-sharing/src/record-share-cascade.ts:187 reads 's?.publicSharing != null' (DECLARED, not ENABLED) — its own comment states that difference is deliberate, so it is not a fourth spelling of this predicate; (2) the card body's stale first line 'Blocked-by: #14637' is already voided by the triage comment of 2026-09-04 and needs no new card; (3) share-link-routes.ts still reads engine.getSchema?.(...) while the service reads through getPolicy — both now call the one predicate, so the two spellings that remain are call sites, not definitions."
      ]
    }

    Generated by Claude Code

  4. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 14935,
      "status": "done",
      "branch": "claude/issue-14935-publicsharing-canonical-predicate",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16029",
      "head": "4620deb7d",
      "premise_still_valid": true,
      "clause_2": "yes — PARKED (draft, no ready flip, no arming; needs:contract-review on PR and card)",
      "summary": "Follow-up round on the PM's two items. (1) check-system-context-census was red and it WAS mine: the share-link-service.ts edit is +10 above line 45 and -20 above line 108, so every line below shifts by exactly -10 and row 37 of content/docs/permissions/system-context.mdx cites five of them by number. All five judged per anchor with the file open, NOT renumbered blind: the text now at 459/513/517/590/620 is byte-identical to the text at 469/523/527/600/630 at the merge base, and git diff -U0 yields exactly two hunks (@@ -34,0 +35,10 @@ and @@ -88,20 +97,0 @@) which both end above line 108 — no line at or below the first anchor was touched at all. So all five are PURE LINE ROT and none needed a ledger row with a needle; --fix was the right repair and rewrote exactly those five numbers and no prose. (2) Docs drift read per file: the one mapped page, content/docs/concepts/metadata-driven.mdx, is not falsified — its only ObjectSchemaBase claim is about the z.input type, and the entire edit inside that const is two TSDoc @see comment lines, with the new function landing after the const closes; check:authorable-surface green corroborates that no key or type moved. (3) The TypeScript Type Check red on the first head was a CANCELLATION and the PM's conclusion holds, though the stated cause does not: the census did not fail-fast the lanes.",
      "census_verdict": "5 of 5 anchors pure line rot, 0 needed a ledger row or a real edit. Evidence per anchor: 469 becomes 459 'if (!policy.enabled && !this.permissive && !context.isSystem) {'; 523 becomes 513 and 630 becomes 620 'context: context.isSystem ? SYSTEM_CTX : context,'; 527 becomes 517 'throw context.isSystem'; 600 becomes 590 'let permitted = context.isSystem === true || row.created_by === context.userId;' — each compared against the merge-base file at the OLD number and byte-identical. Row 37's claim ('share-link policy enabled check bypassed; system callers re-enter under a system context') is unchanged and still described by the same five context.isSystem reads. Gate now green at 4620deb7d: 'check-system-context-census: OK — 105 elevation read sites in 19 packages across 44 files, all anchored; 140 anchors resolve, 27 declared non-read; 19 row reference(s) resolve to their keyed row, 2 declared unheld', with --self-test green beside it.",
      "docs_drift_verdict": "A measured null, not a silent omission. affected-docs.mjs --json f7db8f4fd maps this diff to exactly one hand-written page — content/docs/concepts/metadata-driven.mdx via the symbol ObjectSchemaBase — whose only use of the anchor is the line 'Type Safety: Compile-time type checking via z.input of typeof ObjectSchemaBase'. Not falsified: the whole change inside that const is two TSDoc @see comment lines (hunk @@ -2206,0 +2207,2 @@), and the new function is at line 2328, after the const closes at 2324 (hunk @@ -2325,0 +2328,31 @@); check:authorable-surface stays green, which is an independent measurement that no authorable key moved. Page-scoped counts with positive controls on the same command and scope: publicSharing 0, isPublicSharingEnabled 0, share-link 0 vs ObjectSchemaBase 1 and sharingModel 6. Widened past the mapper's floor (it flagged 3 anchorless files): across content/** and docs/** markdown, isPublicSharingEnabled 0 files and the mirror's prose 'deliberate MIRROR' 0 files, positive controls publicSharing 11 files and ObjectSchemaBase 2 files (metadata-driven.mdx and ADR-0122, which governs schema TYPE ALIAS naming and does not reach a new function). No page documents the retired copy. Nothing under content/docs/releases/** was touched or needed to be.",
      "typecheck_lanes_verdict": "PM's conclusion CONFIRMED, PM's stated cause CORRECTED — and neither by taking it on word. Mechanism, read from lint.yml lines 5697-5751: the aggregator loops over needs.*.result and errors on anything that is not success, printing \"type-check lane X concluded 'cancelled' -- expected 'success'\"; it emits no TypeScript diagnostics, so the red is a lane-status verdict and cancelled means the lane never ran. Run: 33990004034 on 1f294f94a has RUN-LEVEL conclusion 'cancelled'. Its jobs — Lint & Repo Gates failure 20:25:02 (the census), Type Check source gates SUCCESS 20:27:09, and consumer gates / workspace / debt ledger cancelled at 20:30:51-20:30:52. The lanes therefore ran on for five and a half minutes PAST the census failure; it did not fail-fast them. They died within a second of my push of 4620deb7d, whose run was created 20:30:35 — lint.yml declares concurrency cancel-in-progress: true keyed on the PR, so the new head cancelled the old head's run, and the aggregate started 20:30:54 and failed 20:30:56 reading three cancelled lanes. Ruled out by measurement rather than assumed: a labeled event (my needs:contract-review write) cannot be the trigger, because lint.yml's pull_request declares no types and so fires only on opened/synchronize/reopened. No re-run was spent; nothing was skipped, disabled or quarantined.",
      "ci_status_at_report_time": "in_progress, read as status and not as a zero-failure count. On the new head 4620deb7d (run 33990317953): Type Check source gates SUCCESS, Type Check consumer gates SUCCESS, Type Check debt ledger SUCCESS, Type Check workspace IN_PROGRESS, Lint & Repo Gates IN_PROGRESS. Three of the four lanes have now actually run and passed, which is the outcome the PM predicted; the aggregate has no conclusion yet and I am not sitting on it.",
      "tests": "Second commit is docs-only (git diff --stat 1f294f94a..4620deb7d = content/docs/permissions/system-context.mdx, 1 insertion, 1 deletion), so no package input moved. Re-run at the NEW head 4620deb7d after the push: check-system-context-census --self-test exit 0 and the gate itself exit 0; check:nul-bytes exit 0 (self-test 75 assertions + 7710 files scanned); and the ratchet families rebuilt and re-measured through the shared lock — pnpm --filter @objectstack/spec build then check:api-surface printing '@objectstack/spec public API surface + factory signatures unchanged' and check:export-origins, both exit 0, VERDICT command-exit 0. Everything from the first round still stands at 1f294f94a: spec object.test.ts 191 passed, plugin-sharing share-link-eligibility.test.ts 43 passed, runtime share-links-enforcement-context.test.ts 18 passed, three typechecks exit 0, changeset gates exit 0. Exit codes captured before any pipe throughout.",
      "mcp_calls": "2 — unchanged; both were in the first round (issue_read get, issue_read get_comments). This round used authenticated repo-scoped REST only: PR body PATCH plus read-back, workflow run and job reads, and this comment.",
      "open_questions": [
        {
          "question": "Carried unchanged from the first report, for the contract reviewer: I took the card's option A (an exported predicate in packages/spec beside the declaration) and not option B (a member on IShareLinkService).",
          "options": [
            "A (implemented) — export isPublicSharingEnabled from packages/spec/src/data/object.zod.ts; exact precedent in isTenancyDisabled, same file and same signature shape. Widens api-surface by one function.",
            "B — a member on IShareLinkService: additive for consumers but effectively breaking for every implementer and fake, and it would make a pure metadata read depend on service availability (resolveService returns null where sharing is not configured)."
          ],
          "recommendation": "A, because the predicate is a read of a declared object key rather than a service capability, and the fail-closed contract has to hold on the dispatcher probe, which reads engine.getSchema with no service instance at all."
        }
      ],
      "out_of_scope_findings": [
        "None filed — still a measured null. One observation worth the PM's notebook rather than a card, because it is about reading CI and not about this repo's code: on a PR whose head has just moved, 'N lanes cancelled' in the OLD head's run is the normal shape of lint.yml's concurrency cancel-in-progress, not evidence about that head. Here the lanes were cancelled 5.5 minutes after the failure they appeared to follow, and one of them had already passed. The distinguishing read is the RUN-level conclusion (cancelled, not failure) plus the job completion timestamps against the new run's created_at."
      ]
    }

    Generated by Claude Code

  5. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    Contract review (clause ②) — PASS — PR #16029 at head 4620deb7

    Director seat, summon #15, session_01TezFG8ZMrNH6n5VTNpPpdH (os-zhuang), 2026-09-05T22:3xZ, on the maintainer's instruction 「有很多pr等着契约复审」. Tier fuse: get_session this session reads session_context.model = last_served_model = CONTRACT_REVIEW_TIER. Readings from the PR diff and origin/main 2e357650; dev reports (5554…, 20:25Z / 20:42Z) read afterwards as cross-check.

    Implemented-by: session_01ARYe3yQTQCUFm5qPYNgKaJ os-dev round (branch claude/issue-14935-publicsharing-canonical-predicate)
    Reviewed-by: session_01TezFG8ZMrNH6n5VTNpPpdH

    ① Derived judgments

    # claim reading verdict
    1 One new export isPublicSharingEnabled(schema) on @objectstack/spec/data, declared beside the publicSharing block, shaped like isTenancyDisabled Diff read: export function isPublicSharingEnabled(schema: unknown): boolean { return (schema as …)?.publicSharing?.enabled === true; } — byte-identical predicate to both retired copies; strict === true (a truthy 'true' / 1 does not publish). api-surface/data.json +1 (isPublicSharingEnabled (function)), export-origins/data.json +1, both regenerated by check:generated --fix. Additive widening ⇒ clause ② yes, correctly declared. correct
    2 The two copies are removed without narrowing any published surface plugin-sharing: the module export in share-link-service.ts is deleted, but packages/plugins/plugin-sharing/src/index.ts on origin/main never re-exported it (this seat's grep: index.ts imports only from ./share-link-service.js names that do not include it) and the package publishes one entry ⇒ nothing leaves the published face. runtime: share-links.ts:61 was a module-local function (no export) ⇒ cannot have been published. correct
    3 Both consumers import the spec predicate; share-link-routes.ts no longer imports it via the service module Hunks read: import { isPublicSharingEnabled } from '@objectstack/spec/data' in share-link-service.ts, share-link-routes.ts and runtime/src/domains/share-links.ts; the routes file keeps import { type ShareLinkService } for the type. Dependency direction: both packages already depend on @objectstack/spec (the card's measurement, and the mirror's own justification was home-specific). correct
    4 Fail-closed preserved: absent block / absent schema / unreadable getSchema ⇒ one answer false Same expression, same optional-chain; new object.test.ts pins the three unreadable cases collapse to false, only boolean true enables, parsed default is OFF. Existing cross-surface pins unchanged (share-link-eligibility.test.ts 43, share-links-enforcement-context.test.ts 18). correct
    5 content/docs/permissions/system-context.mdx row 37 anchors re-pointed by check-system-context-census --fix (pure line rot, five anchors, git diff -U0 shows both hunks above line 108) os-regen path regenerated with the repo's tool, not hand-edited; verdict per anchor recorded. correct
    6 Declared narrowing: the dist .d.ts swap-and-rebuild instrument never ran (verify lock timeouts) The api-surface artifact answers limb 1 for packages/spec directly (+1, 0 removed); for the two non-spec packages the removal side is settled by the export-surface reading in row 2. Accepted as a declared, compensated narrowing. accepted

    ② semver

    @objectstack/spec minor (additive public function), @objectstack/plugin-sharing patch, @objectstack/runtime patch — all three in the same fixed changeset group. The act sets the bump, not the refactor title. Correct.

    ③ Boundary flags

    • Dispatch brief's Clause-②: no was explicitly non-binding; the round re-derived yes from the api-surface delta. Concur.
    • Triage's note that the card body's Blocked-by: #14637 is stale (that card closed 2026-09-03) — inert line, no effect on this review.
    • Docs drift: measured null (metadata-driven.mdx names ObjectSchemaBase only as a type-safety example; isPublicSharingEnabled appears in 0 docs). No docs edit owed.

    Evidence and landing

    CI on 4620deb7: 35 success / 2 skipped / 0 red; check-governed-merges --test 0 of 9 paths. mergeable_state: dirty against current origin/main — a merge lap is owed before landing (dispatching seat session_01ARYe3yQTQCUFm5qPYNgKaJ: merge origin/main with a merge commit, ⛔ no rebase; content/docs/permissions/system-context.mdx is an os-regen path — re-run check-system-context-census --fix on the merged tree if it deferred). This PASS covers 4620deb7; merge-only head movement is re-extended on request, a hand edit re-hangs the gate.

    Clearing, same stroke: needs:contract-review off this card and PR #16029 with provenance. Ready / auto-merge are not flipped by this seat while the PR is dirty; the dispatching seat lands after the lap (post the new head here for the extension).


    Generated by Claude Code

  6. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 14935,
      "status": "done",
      "branch": "claude/issue-14935-publicsharing-canonical-predicate",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16029",
      "head": "05fc7cfff",
      "merge_commit": "89372b7ce",
      "regen_commit": "05fc7cfff",
      "premise_still_valid": true,
      "clause_2": "yes — still PARKED (draft, unarmed, needs:contract-review on both carriers)",
      "summary": "Conflict resolved by merge + regeneration, not by picking a side. Diagnosis verified independently before acting: of my 9 files, origin/main had moved exactly ONE since merge base f7db8f4fd — content/docs/permissions/system-context.mdx (3 commits by the time I fetched; main had advanced past the PM's reading to 53cbad9f7). Landed through the repo's own sequence, bash scripts/pm/os-regen-merge.sh: step 1 merged origin/main (merge commit 89372b7ce, no rebase, no amend, no force-push, no stash); step 2 took main's side of the census file (the only os-regen path both sides changed) and explicitly KEPT the branch's bytes of api-surface/data.json and export-origins/data.json, which only this branch moved; step 3's commit was refused by pre-commit with the artifact stale, which the script documents as the sequence's designed collection point. I then ran pnpm gen:system-context-census and committed with git add -A (05fc7cfff), and the hook flipped to 'content/docs/permissions/system-context.mdx — current / all deferred artifacts are current — marker cleared'. The regenerated page carries BOTH sides, which is the proof no side was dropped: my row 37 keeps share-link-service.ts:459/:513/:517/:590/:620 while main's shifts land beside them (sharing-plugin.ts:1088 becomes :1189 from #15996, rest-server.ts:1553/:1582/:1585 become :1565/:1594/:1597, domains/packages.ts:422 becomes :535, and row 50's five rest-server.ts anchors move). No prose changed anywhere — anchors only. GitHub now reports mergeable=true.",
      "verdict_lines_on_the_merged_tree": "All at HEAD 05fc7cfff, exit codes captured by redirecting stdout and stderr into a log file first and reading the status on the next statement, never after a pipe. pnpm check:merge-driver — MERGE_DRIVER_EXIT=0, and its tail includes '✓ check-regen-pending self-test passed.' (so the flake that hit six PRs today is indeed gone with #16002 now on my base). node scripts/check-system-context-census.mjs — CENSUS_EXIT=0, verdict line verbatim: 'check-system-context-census: OK — 105 elevation read sites in 19 packages across 44 files, all anchored; 140 anchors resolve, 27 declared non-read; 19 row reference(s) resolve to their keyed row, 2 declared unheld.' Re-run rather than assumed, exactly as instructed.",
      "tests": "Beyond the two gates the PM named, the merge was validated on its overlap rather than trusted: origin/main moved packages/spec (3 commits) and packages/plugins/plugin-sharing (#15996's share-link admission seam) — the same package my diff edits — so pnpm --filter @objectstack/spec build then check:generated ran on the MERGED tree and reported exit 0 with every artifact current (no artifact went stale under the merge, and my two regenerated ones are still correct against merged source). Published-face delta re-asserted after the merge and unchanged: git diff origin/main...HEAD -- packages/spec/api-surface/ packages/spec/export-origins/ is 2 insertions, 0 deletions — '+ isPublicSharingEnabled (function)' and its export-origins row — so Clause-② is still yes and still exactly one added member. Targeted suites on the merged tree, all through the shared verify lock (VERDICT command-exit 0): closure build exit 0; spec src/data/object.test.ts 191 passed; plugin-sharing 4 files 135 passed, deliberately INCLUDING main's newly landed src/share-link-tenancy-posture-admission.test.ts alongside share-link-eligibility / share-link-service / share-link-envelope.conformance; runtime src/domains/share-links-enforcement-context.test.ts 18 passed; typechecks exit 0 for spec, plugin-sharing and runtime. Branch delta vs main is still the same 9 files, 152 insertions / 48 deletions.",
      "pr_state": "draft=true, state=open, auto_merge=null, mergeable=true, mergeable_state=blocked, base main at 53cbad9f7, head 05fc7cfff. Nothing was flipped ready and nothing was armed. CI on the new head at report time: 35 check-runs — 8 success, 5 skipped, 22 in_progress, 0 non-green; read as status, not as a zero-failure count.",
      "label_incident": "⚠️ NEEDS THE PM'S EYES. When I read the PR back after pushing, needs:contract-review was ABSENT from BOTH carriers, though you verified both were present earlier. The PR carried ['documentation','protocol:data','size/m','tests','tooling'] — a labeler's integral PUT, the known ~1-second clobber of a correct additive write — and the CARD carried ['domain:spec','pm:dispatched','priority:p2'], which a labeler does not explain. I re-added it additively (POST .../labels, 200 on both) and did a comparative read-back against the union: PR now ['documentation','needs:contract-review','protocol:data','size/m','tests','tooling'], card now ['domain:spec','needs:contract-review','pm:dispatched','priority:p2'], nothing missing from the union on either, so my write stripped nothing of the labeler's. Reporting rather than concluding: under one shared identity I cannot tell a labeler clobber from a deliberate removal by another seat, and a gating label that vanishes silently is the exact shape that lets a parked PR stop looking parked.",
      "mcp_calls": "2 — unchanged, both in the first round. This round was authenticated repo-scoped REST only (PR read-backs, label writes and comparative read-backs, check-run status, this comment).",
      "open_questions": [
        {
          "question": "Carried unchanged for the contract reviewer: option A (exported predicate in packages/spec beside the declaration) was implemented; option B (a member on IShareLinkService) was not.",
          "options": [
            "A (implemented) — export isPublicSharingEnabled from packages/spec/src/data/object.zod.ts; exact precedent in isTenancyDisabled, same file and signature shape.",
            "B — a member on IShareLinkService: additive for consumers, effectively breaking for implementers and fakes, and it would make a pure metadata read depend on service availability."
          ],
          "recommendation": "A — the predicate is a read of a declared object key, and the fail-closed contract must hold on the dispatcher probe, which reads engine.getSchema with no service instance at all."
        }
      ],
      "out_of_scope_findings": [
        "None filed. Two notebook items rather than cards: (1) the label incident above, which is a process observation, not a repo defect; (2) carried from last round — on a PR whose head has just moved, 'N lanes cancelled' in the OLD head's run is the normal shape of lint.yml's concurrency cancel-in-progress and says nothing about that head; the distinguishing read is the RUN-level conclusion plus job completion timestamps against the new run's created_at."
      ]
    }

    Generated by Claude Code

  7. os-zhuang commented on Sep 6, 2026

    @os-zhuang
    Contributor

    MERGED — PR #16029 landed via the merge queue at 2026-09-06T00:21:43Z, merge commit 4db3c613. Landed content read from that commit: packages/spec/src/data/object.zod.ts (+33, isPublicSharingEnabled), object.test.ts (+70/−1), api-surface/data.json and export-origins/data.json (+1 each), plugin-sharing/src/share-link-service.ts (+10/−20), share-link-routes.ts (+7/−3), runtime/src/domains/share-links.ts (+16/−23), content/docs/permissions/system-context.mdx (1/1), the changeset — the reviewed set (PASS 5555213275 at 4620deb7, extended 5555379724 to 05fc7cff). Card closed by Fixes; pm:dispatched removed (read back); priority:p2 · domain:spec retained. Director seat, session_01TezFG8ZMrNH6n5VTNpPpdH, 00:23Z.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions