Repository navigation
docs(skills): name the complete public-form opt-in the anonymous form endpoints read - #21650
Conversation
… endpoints read
The objectstack-api public-form section and the objectstack-ui assembly row named
two of the three `sharing` keys the anonymous form endpoints require. Both now
name `enabled: true` (schema default `false`), `allowAnonymous: true` and a
`publicLink` slug, the `404 FORM_NOT_FOUND` answer when any one is missing, and
the walled-posture condition with its `tenancy: { enabled: false }` remedy.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk
Contract reviewServed-tier: Read-only shape: the diff against the merge base 15fe567, card #21567 with every comment, and this head's check-runs; nothing built or run locally. Reviewed by the dispatch seat in seat (served tier equals the constant's value, read from ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
维护者速读(终稿)— PR #21650(#21567)· skills seat 1 · 2026-10-04T00:27Z改了什么: 两份对外发布的技能页里描述「匿名公开表单」开关的句子。 为什么改: 运行时规则已在 PR #21566 落到 风险与代价(含回滚): 纯技能文本,不碰 席位意见: 建议批准。每一句都对照 你要做的(一个动作): 在 PR #21650 上给一次 APPROVED review;批准后由席位清标、ready、挂 auto-merge 入队。 |
…nd an unknown --object is refused (objectstack-ai#21662) Fixes objectstack-ai#21644 Clause-②: no A deployment-level flag is now written only by a full-scope run. `os migrate value-shapes` and `os migrate files-to-references` narrowed by `--object` apply their fixes, record no deployment flag, and say so. A full-scope `--apply` records the flag exactly as before. Across the family (`value-shapes`, `files-to-references`, `summary-nulls`, `duplicates`), an `--object` name the deployment does not declare is refused with `OBJECT_NOT_FOUND` before anything is read or written. The refusal names the unknown name and the declared objects. This follows triage ruling `5974774596`. `--apply --object` is not refused. ## Measured first (base `759dbe9ed3`) ### A1. The reach, at the public door (hypothesis confirmed) A throwaway SQLite project held three objects, one of them `os21644_site` with a `location` field. A served-shape boot seeded one clean row per object. Then one off-shape value was written past the write path: the site's `geo` stored as `{latitude, longitude}`. The fresh-datastore attestation had recorded both ADR-0104 flags as verified at birth, so the flag table was emptied first. - `os migrate value-shapes --json` exited 1, with `gatePassed: false` and `blocking: 1`. - `os migrate value-shapes --object os21644_sitee --apply --yes --json` (misspelled) exited **0**. It answered `gatePassed: true` with `scannedObjects: []`, and the `adr-0104-value-shapes` row read **verified** (`verified_at` set, `blocking: 0`). ### A2. The census, one row per command | command | `--object` | `--apply` records a deployment flag | where it is written (base) | unknown `--object` on base (measured) | | --- | --- | --- | --- | --- | | `value-shapes` | repeatable | `adr-0104-value-shapes` | the CLI: `recordDataMigrationRun` at `value-shapes.ts:221` | exit 0, `scannedObjects: []`; with `--apply`, the flag is recorded **verified** | | `files-to-references` | repeatable | `adr-0104-file-references`, then the column step's `columns_moved_at` | the producer: `runFilesToReferencesMigration` at `files-to-references-migration.ts:120`; the column stamp is `recordFileColumnMove` in the CLI (`files-to-references.ts:472`) | exit 0, both scans' `scannedObjects: []`; with `--apply`, the flag is recorded **verified**, **and the column step moved `os21644_product.image` and stamped `columns_moved_at`** | | `summary-nulls` | repeatable | none (its header: "No deployment flag, deliberately") | none | exit 0, `fields: []`, on a dry run and on `--apply` | | `duplicates` | single | none: no `--apply`, and it writes nothing | none | exit 0, `scanned: []`, `filter: { object: 'os21644_sitee' }` | A correctly spelled narrowed `files-to-references --apply` on base also recorded the flag verified and moved the column. Every scan draws its default candidates from the same registry: `options.objects ?? Object.keys(engine.getConfigs())` in `scanValueShapes`, `backfillFileReferences`, `verifyFileReferences` and `backfillSummaryNulls`, and `stack.allObjects()` for `collectScanTargets`. Each keeps only the candidates it covers, which is where an undeclared name was dropped. ### A3. The narrowed run - **CLI-recorded flag (`value-shapes`).** The flag write is skipped on a narrowed run, whether the run passes or fails. `--json` carries `flag: null` and `filter: { objects }` (`null` on a full-scope run, the shape `duplicates` already keeps). Both faces print one sentence: the run was narrowed, no deployment flag was recorded, and the command that records one is the same command without `--object`. - **Producer-recorded flag (`files-to-references`).** `runFilesToReferencesMigration` skips the write when it is given `objects`. This is the declared `service-storage` path only. Its `flag` result is `null` on a narrowed run. The CLI prints the same sentence and carries `filter`. - **The column step (`files-to-references`) does not run on a narrowed run.** The census row above is why. The step retypes every single-value media column in the database on the authority of the gate, and a narrowed gate vouches only for the named objects. Its stamp also requires a verified flag, which a narrowed run no longer records. Left running, a narrowed `--apply` would move columns and then fail to record the move. It now returns a stated skip, `narrowed_run`, and the human face says why. - **What "narrowed" means.** Any `--object` narrows, even a list that names every declared object. The flag is earned by the one spelling that means "every object", which is a run without `--object`. Treating a full list as full scope would need a second definition of "the whole deployment", checked against the registry of the moment, and that registry changes with the composition between two runs. The operator also gets one unambiguous prescription. - **Deviation from the dispatch wording ("skips it when `objects` is non-empty").** The producer treats **any** `objects` as narrowed, `[]` included. A scan handed `[]` walks nothing (`[] ?? …` is `[]`). A non-empty test would therefore record a verified flag over an empty scan, the card's own defect at the producer's API. A unit pin holds this. - The prompts and closing lines that promised a flag on a narrowed run now say it records none. ⛔ `--apply --object` is not refused, and the full-scope write is unchanged. ### A4. Unknown `--object` - **Checked against the registry the command's own boot resolved, before the scan.** For `value-shapes`, `files-to-references` and `summary-nulls` that registry is `Object.keys(engine.getConfigs())`. For `duplicates` it is the names of `stack.allObjects()`. These are the same sets the scans draw from, so the refusal and the scan judge one population. There is no `packages/objectql` edit and no scanner edit. - **The refusal is objectstack-ai#21643's.** It is `objectNotFoundError` from `@objectstack/core`: `code: 'OBJECT_NOT_FOUND'`, `status: 404`, and `object` naming the first unknown name. Its message names every unknown name and the declared objects, sorted. There is no new error code. `value-shapes`, `files-to-references` and `summary-nulls` answer `{ error, code }`, as `unmapped-columns` does. `duplicates` keeps its own error shape, `{ error: 'report_failed', detail, code }`: its catch now passes `errorCodeFields` through. - **The list is the declared set, not the covered subset.** Computing the covered subset for `value-shapes` needs `isScannableValueShapeField`, which `@objectstack/objectql` does not export, and that package is fenced. The declared set is also exactly the accept set. A declared object the command has nothing to check on is accepted, because an empty answer about a real object is true. On the fixture boot the list is 12 names, platform objects included. - **Clause-②: no stands as the claim declared it.** A misspelled name moves from exit 0 to exit 1, which is the ruled correction of a wrong answer. Every declared name and `--apply --object` are still accepted. ## Changes - `packages/cli/src/utils/migrate-object-scope.ts` (new): `refuseUndeclaredObjects`, `isNarrowedRun` and `narrowedFlagNote`, shared by the four commands. - `packages/cli/src/commands/migrate/value-shapes.ts`: refuses an unknown name, skips the flag on a narrowed run, adds `filter`, and adjusts the narrowed prompt and closing lines. - `packages/cli/src/commands/migrate/files-to-references.ts`: refuses an unknown name, adds the `narrowed_run` column-step skip, adds `filter`, and adjusts the narrowed prompt and closing lines. - `packages/cli/src/commands/migrate/summary-nulls.ts` and `duplicates.ts`: refuse an unknown name. `duplicates`' error document carries the error's `code`. - `packages/services/service-storage/src/files-to-references-migration.ts`: skips the flag write when given `objects`. - `content/docs/deployment/cli.mdx`: one paragraph under "Data migrations" (`--object` narrows, an unknown name is refused, only a full-scope run records a flag), and the two `--object` example comments. - `.changeset/21644-narrowed-apply-flag.md`: `@objectstack/cli` patch and `@objectstack/service-storage` patch, `Clause-②: no`. `packages/objectql`, `packages/platform-objects`, `packages/spec`, every other `service-storage` path, and `content/docs/releases/` are untouched. ## Pins - **`object-scope.integration.test.ts`** spawns the CLI against SQLite, one database copy per run, and is one enumeration over the census (`FAMILY`). - A narrowed `--apply` (`value-shapes`, `files-to-references`): exit 0, `flag: null`, `filter: { objects }`, no flag row, and the note on stderr naming the full-scope command. - A full-scope `--apply`: the flag recorded verified, in the document and in the row. - `summary-nulls` and `duplicates`: no flag row, narrowed or not, as before. - A narrowed `--apply` after an earned flag leaves that row byte-equal. - `files-to-references` narrowed: `columnMove: null` and `columnsMovedAt: null`. Its full-scope control moves `os21644_product.image` and stamps it. - Unknown `--object`, on all four: exit 1 and `OBJECT_NOT_FOUND`, naming the name and the declared objects. The one document is the refusal and no report, no flag row is written, and the app rows are unchanged. The human face exits 1 and names it. - The measured repro. Control: the full-scope scan sees `blocking: 1` and exits 1. The misspelled `--object --apply` exits 1 with `OBJECT_NOT_FOUND`, and the flag stays unrecorded. Spelled right, the narrowed run finds the value, exits 1, and still records no flag. - **`migrate-object-scope.test.ts`** (unit): the envelope (`code`, `status`, `object`), every unknown name named once, the declared list sorted, the empty-registry message, the accepted cases, and what `isNarrowedRun` treats as narrowed (an empty list and a full list both narrow). - **`files-to-references-migration.test.ts`** (`service-storage`, beside the producer): - a narrowed apply converts and records no flag; - a narrowed failing apply records nothing; - a narrowed apply leaves an earned flag row equal; - `objects: []` records nothing. ## Reverse verification (implementation committed first; all three legs re-run at the final head `fe988c20f0`) Each leg ran through `node scripts/ablation-replace.mjs` in wrap mode, under a script trap that restores from `HEAD`. The spawned CLI loads its commands from `src/` through `bin/run-dev.js`. In the first round `packages/cli/dist` did not exist. In the final round it held a build of the unmutated source, and legs 1a and 2 still went red, which shows the spawned CLI read the mutated `src/`. The `service-storage` unit pin imports the producer from `src/`. Neither needed a rebuild. - **Leg 1a, the narrowed-run skip in the CLI** (`value-shapes.ts`): - The anchor `if (apply && !narrowed) {` became `if (apply) {`: anchor 1 to 0, replacement 0 to 1, blob `9f241dc2` to `d3a5c236`. - **3 red, 17 green.** Red: the `value-shapes` narrowed pin, the earned-flag-unchanged pin, and the spelled-right repro. Green: both full-scope controls (the column-step control among them), the `files-to-references` narrowed pin (its skip is the producer's), and every unknown-name pin. - Restored: blob `9f241dc2` equals `HEAD`, and `git diff HEAD` is empty. - **Leg 1b, the narrowed-run skip in the producer** (`files-to-references-migration.ts`): - The same anchor and replacement: anchor 1 to 0, blob `1aa9fea2` to `d4bb5002`. - **4 red, 6 green.** Red: all four narrowed pins (passing, failing, earned-flag-unchanged, and `objects: []`). Green: the six original pins, the full-scope apply among them. - Restored: blob `1aa9fea2` equals `HEAD`. - **A first round is recorded here because one of its readings was vacuous.** At `80e5eda6ea` this leg read 3 red and 7 green: the earned-flag-unchanged pin stayed green under the mutation, because the fake engine's rewrite landed in the same millisecond as the earned row. The pin now dates the earned row in the past (`fe988c20f0`), and the re-run is the reading above. - **Leg 2, the unknown-name refusal** (`migrate-object-scope.ts`): - The anchor `if (unknown.length === 0) return;` became `if (unknown.length >= 0) return;`: anchor 1 to 0, replacement 0 to 1, blob `b78a88b4` to `9286a990`. - **Unit: 3 red, 3 green.** Red: the three refusal cases. Green: the accepted cases and the two `isNarrowedRun` cases. - **Integration: 10 red, 10 green.** Red: all eight unknown-name pins (two per command, on all four), the human face, and the misspelled repro. Green: every narrowed and full-scope pin, and the repro's control. - Restored: blob `b78a88b4` equals `HEAD`. - In the first round, the `duplicates` "refused before anything was read" pin stayed green under this mutation: it asserted only on a key that report never carries. It now asserts that the one document is the refusal, which reds on all four commands. After all legs, `git diff HEAD` was empty and `git status --porcelain` was clean. ## Local verification (final head `fe988c20f0`, on base `759dbe9ed3`) `origin/main` was `759dbe9ed3` for the whole verification. Just before this PR opened, it gained four commits, `f40bb3217f` to `1a230548cf` (objectstack-ai#21649, objectstack-ai#21632, objectstack-ai#21648, objectstack-ai#21650). None of them touches this diff's paths (`packages/spec`, `metadata-protocol`, `service-automation`, `lint`, skills and docs references), so they were not merged in. CI runs on the merge ref. - **Builds.** The CLI's dependency closure (`turbo run build --filter=@objectstack/cli^...`) gave VERDICT 0. `@objectstack/service-storage` was rebuilt after the producer change (exit 0), and `@objectstack/cli` was built (exit 0). A repo build for the gate prerequisites gave VERDICT 0 (turbo: 72 tasks, 71 cached). - **`@objectstack/cli` typecheck** (`tsc --noEmit` plus `check:test-typecheck`): exit 0 at `80e5eda6ea`. No CLI file changed after that commit. - **`@objectstack/cli` unit project in full** at `80e5eda6ea`: - 255 of 257 files passed, with 3742 tests passed and 29 skipped (the two files below). - The other two files, `test/published-subpath-{console,hook-body}.pin.test.ts`, refused before testing because `packages/cli` was not built (their own prerequisite message). After the CLI build, both passed: 2 files, 29 tests. - **`@objectstack/service-storage`**: typecheck exit 0, and the full suite at `fe988c20f0` passed 41 files and 633 tests. - **`os migrate` integration pins on built packages**, at `80e5eda6ea`: - this PR's pin plus the absent-database roster: 2 files, 59 passed; - the one-shot family plus `duplicates.integration`: 2 files, 79 passed. - **Gates.** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths) derived 97 commands at `fe988c20f0`. - All 97 ran there, and each exited 0. - `--ran` with exit codes: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN. - An earlier round at `80e5eda6ea` had three gates answer `PREREQUISITE NOT MET` (exit 3): `check:skill-examples`, `check:dual-build-cjs-loads` and `check:i18n-coverage`. They read packages outside the CLI closure. The repo build cleared them. - **Full `pnpm lint`** (`eslint . --no-inline-config` over the whole repo): exit 0 at `fe988c20f0`, with nothing printed. - **No exported symbol was renamed or moved,** so the liveness-ledger anchor check had nothing to read. ## Acceptance notes - **A narrowed run's counterexample is not recorded.** The ruling says a narrowed `--apply` records no flag, so it records none even when it finds a violation. Such a counterexample is deployment-level evidence, since one off-shape value disproves "every value is on shape". The operator still gets exit 1 and the findings, and the next full-scope run closes the gate. This is an observation, not a filing. Carrier: none. - **The declared list includes platform objects.** It is 12 names on the fixture's lean boot, and a deployment that composes more plugins prints more. A long list in an error message is the price of naming the exact accept set. Carrier: none. - **A narrowed `value-shapes --apply` still takes the plain (DDL-performing) boot** even though it now writes nothing. That is unchanged, and the boot paragraph in the docs still describes it truthfully. Carrier: none. --- _Generated by [Claude Code](https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #21567
Clause-②: no
What
The published
objectstack-apiskill's public-form section named two of the threesharingkeys the anonymous form endpoints read, and theobjectstack-uiskill's UI-assembly row repeated the omission. An AI following either authors a form that bothGET /api/v1/forms/:slugandPOST /api/v1/forms/:slug/submitanswer404 FORM_NOT_FOUND. Both now state the complete opt-in as the merged rule reads it, plus the walled-posture condition and its remedy.Skill text only (
skills/**, Tier H). Nothing underpackages/**is touched; the shipped migration prose inpackages/spec/src/migrations/**stays as written (triage ruling 5967394091, "Not here").The rule the text now describes (read on
origin/main15fe567)packages/spec/src/ui/sharing.zod.ts:95—enabled: z.boolean().default(false).describe('Enable public sharing');:102—allowAnonymous: z.boolean().optional().default(false).packages/metadata-core/src/anonymous-form-intake.ts:66-68— the three checks (s.enabled !== true,s.allowAnonymous !== true,publicLinka non-empty string);:57-59—publicFormSlugfolds/forms/x,forms/xandxinto one slug.packages/rest/src/rest-server.ts:10807and:10977— both doors answer404 FORM_NOT_FOUNDwheneverresolveFormBySlugreturns null, which it also does for a form the posture withholds (:10781-10789).packages/metadata-core/src/anonymous-form-intake.ts:212— a wall is in force only on a posturepostureEnforcesWalladmits (group/isolated);:223-225— the remedy text: declaretenancy: { enabled: false }on an object whose rows belong to no organization.packages/metadata-protocol/src/runtime-authoring-gate.ts:404—PUBLIC_FORM_INTAKE_UNAVAILABLE = 'public-form-intake-unavailable', awarningthe authoring gate raises for aviewwrite (save and publish).Every
skills/**site that describes the opt-inSweep at 15fe567:
git grep -n -ioverskills/forallowAnonymous,publicLink,anonymous form,public form,forms/:slug,Web-to-Lead,web-to-case,guest_portal,walled.skills/objectstack-api/SKILL.md:77-78— "AnyFormViewdeclared withsharing.allowAnonymous: trueand apublicLinkslug is auto-mounted at:"enableddefault, and the404 FORM_NOT_FOUNDanswer when one is missingskills/objectstack-api/SKILL.md:94— "withallowAnonymous/publicLink"enabled/allowAnonymous/publicLinkskills/objectstack-api/SKILL.md:85-94— the paragraph after the route blockpublic-form-intake-unavailablewarning on save and publish, and thetenancy: { enabled: false }remedyskills/objectstack-ui/SKILL.md:208— the UI-assembly table's public-form rowsharing: { enabled: true, allowAnonymous: true, publicLink: 'slug' }skills/objectstack-data/SKILL.md:188— "public forms" in the list of surfaces a field's conditional rule applies toskills/objectstack-data/references/_index.md:57,skills/objectstack-ui/references/_index.md:48sharing.zod.ts, no opt-in proseNo other hit;
guest_portalandWeb-to-Leadoccur only inside the edited section.Readings
skills/objectstack-api/SKILL.mdskills/objectstack-ui/SKILL.mdskills/*/SKILL.mdTokens are the ratchet's own convention,
ceil(bytes / 4); the gate's lines at 4adec02: "skills/objectstack-api/SKILL.md is 4758 tokens (ceiling 6319; headroom 1561)" and "skills/objectstack-ui/SKILL.md is 3854 tokens (ceiling 3856; headroom 2)". Theobjectstack-uirow paid for its three keys inside the same line: the label "Public / anonymous form" is now "Public form" (the cell still carriesallowAnonymous), and "/ Web-to-Case" and "Auto-exposed" are dropped (the API skill's section keeps "Web-to-Lead / Web-to-Case"). No other line of that file moved; the sibling card #21537 holds the subforms example at :72-85.origin/mainwas still 15fe567 when this PR was opened, so no merge was owed.Tests
All 24 gate families that
node scripts/pm/dispatch-gates.mjs --commandsderives for this diff were run locally at4adec025, each exit captured before any pipe.--ranreconciliation: "24 derived famil(ies) accounted for — 24 run, 0 NOT-MEASURED (a DERIVED zero — all 24 recorded an exit code and none of them is 3)". Verdict lines:node scripts/check-skills-token-ratchet.mjsexit 0 — "✓ check-skills-token-ratchet: 54 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted.";--self-testexit 0 — "65 cases pass".node scripts/check-doc-route-spelling.mjs --advisoryexit 0 — "✓ route-spelling guard (advisory): population clean — every shape-matched literal spells its ledger row.";--self-testexit 0.pnpm check:skill-identifier-livenessexit 0 — "Leg 1: 457 citation(s) over 53 published file(s) checked against 118365 implementation word tokens (0 ledgered exemption(s)); Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s)."pnpm --filter @objectstack/spec run check:skill-docsexit 0 — "✅ Skill docs in sync" (afterpnpm --filter @objectstack/spec buildunder the verify lock,VERDICT command-exit 0).pnpm --filter @objectstack/lint run check:doc-formula-expressions— first run exit 3,PREREQUISITE NOT MET(@objectstack/formulaand@objectstack/lintnot built; "Nothing was measured"); afterturbo run build --filter=@objectstack/formula --filter=@objectstack/lintunder the lock, exit 0.pnpm check:corpus-claim-drift,check:doc-authoring,check:role-word,check:skill-compatibility,check:skill-frame-sync,check:nul-bytes,check:agent-test-spelling,check:cross-package-test-inputs,check:driver-memory-census,check:gitlink-declared,check:pm-governed-merges,check:refd-timer-probe,check:watch-hint-literal,node scripts/check-ci-filter-parity.mjs,node scripts/check-closing-keyword-parity.mjs(and--self-test),node scripts/check-comment-mask-corpus.mjs— all exit 0.grep -naPspelling from the agent rules): clean.Not run locally, CI owns them: the repo-wide
pnpm lint, the type-check lanes and the package test suites — no package source changed.维护者速读(草稿)
改了什么: 两份对外发布的技能页(
objectstack-api、objectstack-ui)里描述「匿名公开表单」开关的句子,原来只写了allowAnonymous: true和publicLink两个键。现在补齐第三个键enabled: true(schema 默认是false),写明三者缺一则两个匿名端点都答404 FORM_NOT_FOUND,并加一句:在group/isolated这类带租户墙的部署姿态下,目标对象若按组织列隔离,表单同样不对外提供,保存与发布时会收到public-form-intake-unavailable警告,补救是对不归属任何组织的对象声明tenancy: { enabled: false }。为什么改: 运行时规则已在 PR #21566 落地到
main:三个键齐全才服务。技能文本是 AI 写元数据的直接依据,按旧文本写出来的表单会被两个端点同时拒绝,而作者不知道为什么。本 PR 让文本与已合并的规则、与SharingConfigSchema的默认值完全一致。风险与代价(含回滚): 纯文本改动,不碰
packages/**,无 changeset(skip-changeset)。objectstack-ui/SKILL.md的 token 棘轮只剩 2 的余量,所以那一行用同一行内的删字付账(行标签「Public / anonymous form」改为「Public form」,去掉「/ Web-to-Case」与「Auto-exposed」);objectstack-api/SKILL.md净增 6 行,在 PM 给的 +6 预算内。回滚:revert 本 PR 即可,没有派生产物。席位意见:
你要做的: 这是 Tier H 受管面(
skills/**),需要你的 APPROVED review;批准后由席位落地。请顺带看一眼objectstack-ui那一行的措辞取舍是否接受(标签缩短、去掉 Web-to-Case)。Acceptance notes
examples/app-showcase/src/ui/views/inquiry.view.ts:10andexamples/app-showcase/src/data/objects/inquiry.object.ts:10, and the header comments ofpackages/qa/dogfood/test/showcase-public-form.dogfood.test.ts:5andshowcase-public-form-withdrawal.dogfood.test.ts:7, narrate the opt-in with the two-key wording; the example view itself declares all three keys (inquiry.view.ts:70-73), as doesexamples/app-crm/src/views/lead.view.ts:124-126.CHANGELOG.md:607carries the pre-rule wording — release-owned history, never edited in a code PR.content/docs/ui/forms.mdx:23and:234already namesharing.enabled: true+sharing.allowAnonymous: true+publicLink; nothing owed there./forms/x,forms/xandxare one slug) is not in the skill text — it did not fit the +6 line budget; both example apps spellpublicLink: '/forms/contact-us', which the UI row'spublicLink: 'slug'placeholder admits.objectstack-fleet[bot](set with-c user.nameon this one commit; the container default isClaude); the trailer pair is the model-free form the pre-push hook accepted.Generated by Claude Code