Repository navigation
docs(readme): state the real milestone, and that positions bind by name - #56
Merged
Merged
Conversation
The status line still read `M0 — scaffold and configuration domain` on a tree that carries M1-M3: the eleven objects, the permission layer, intake, the approval ladder, signing and execution formalities, the post-signature reminder layer, four datasets, three dashboards and both locale bundles. A first-time reader was told this is a scaffold and then found 820 seeded rows. Replaced with one line naming the current state plus a pointer to DESIGN.md 11 and the three remaining cards, rather than a checklist that drifts the same way. The setup section named the seven positions but never said how the assignment binds. `sys_user_position.position` is a plain text column holding a `sys_position.name` - the one field on the row without an `_id` suffix, while `user_id` is a real lookup and `sys_user_permission_set.permission_set_id` is genuinely id-typed. Anyone scripting the setup reaches for an id by analogy, gets `201` with no diagnostic, and ends up with an account holding no permission set and empty navigation. Documented the wire shape, the set a name-spelled row actually grants, the `explain` call that reads it back, and the silent id acceptance (upstream objectstack-ai/objectstack#16712, open). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR
This was referenced Sep 10, 2026
The subsection claimed `position` was "the one field on that row without an
`_id` suffix". Enumerating `SysUserPosition` in
@objectstack/plugin-security@17.4.0 shows fourteen fields, of which only three
carry the suffix - and `granted_by`, `delegated_from` and `certified_by` are all
`Field.lookup("sys_user")` with no suffix at all. The sentence taught a tell that
is falsified three times over on its own object: an operator generalising it onto
`granted_by` would spell a name into a genuine lookup, the mirror image of the
bug the subsection exists to prevent.
Rebuilt the paragraph on the evidence that does hold - the field's own
declaration, `text` with the description "Position machine name (references
sys_position.name)" - and says outright that the field name tells you nothing
here because the object mixes both kinds. The
`sys_user_permission_set.permission_set_id` contrast stays: that one really is
id-typed, which is what makes the wrong guess tempting.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR
zhuangjianguo
marked this pull request as ready for review
September 10, 2026 08:01
This was referenced Sep 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #46
Two
README.mdcorrections, both about prose that was true once and stopped being true. No code, no metadata, no behaviour change —README.mdis the only file in the diff, over two commits (6a86e41then the rework in0a9c340).Branched off
d34b225(post-#55), so the operator table this card was written against is already the newer one; it is untouched here.Edit 1 — the status line
Status: **M0 — scaffold and configuration domain.**became one line naming the current state plus pointers to the two authorities (DESIGN.md§11,docs/backlog). Deliberately not a milestone checklist — that is the shape that drifted in the first place.I verified the completion claim against this tree rather than taking the dispatch's list on trust:
ls src/objects/*.object.ts= 11src/profiles/= 5 permission sets (clm_admin·clm_finance·clm_legal·clm_records·clm_requester),src/sharing/positions.ts= 7 positions,contract.sharing.ts+ FLSsrc/flows/F1 · F5 · F7 present;src/flows/index.tsregisters 12 flows inallFlows, wired atobjectstack.config.ts:191legal-review-sla·turn-stalled·obligation-due·payment-overdue·renewal-notice·expiration-sweep(+_daily-sweep.ts)src/datasets/= 4,src/dashboards/= 3src/translations/en+zh-CN; the i18n gate reports0 missing keys across 2 locale(s)src/flows/index.tsheader: "F8 (e-signature) and F15 (CRM hand-off) arrive with cards 12 and 13";src/skills/does not exist (S1–S6, card 13);content/does not exist (docs site, card 14);src/mappings/does not existdocs/backlog/README.mdmilestone column — 12/13/14 are the only M4 rowsEdit 2 — the position assignment binds by name
New
### Assigning a position from a scriptsubsection, placed after the dev-admin paragraph so the operator narrative and its tables are untouched. Every sentence in it has a reading behind it, on this tree:sys_user_position.positionis a plain text column carrying asys_position.name@objectstack/plugin-security@17.4.0dist/index.mjs:position: Field.text({ maxLength: 100, description: "Position machine name (references sys_position.name)." }), and the object's own description — "Assigns a position (sys_position.name) to a user"user_idis a real lookupuser_id: Field.lookup("sys_user", …)_idsuffixSysUserPositionenumerated out of the same dist:idtext ·user_idlookup ·positiontext ·business_unit_idlookup ·organization_idlookup ·granted_bylookup ·valid_fromdatetime ·valid_untildatetime ·reasontext ·delegated_fromlookup ·last_certified_atdatetime ·certified_bylookup ·created_atdatetime ·updated_atdatetimesys_user_permission_set.permission_set_idis genuinely id-typedpermission_set_id: Field.lookup("sys_permission_set", …)POST /api/v1/data/sys_user_positionis the wire shape@objectstack/client@17.4.0builds a create as POST to the data route joined with the object name (create: async (object, data)→fetch(baseUrl + getRoute("data") + "/" + object, { method: "POST", body: JSON.stringify(data) })), and the route defaults carrydata: "/api/v1/data". The object declaresmanagedBy: "system-data"— "the bucket default is full CRUD"clm_requestersrc/security/bind-position-sets.ts—BINDINGSpairs each position with its own set and then spreadsObject.values(CLM_POSITION).map(p => [p, RequesterSet.name]);LegalSet.name=clm_legal,RequesterSet.name=clm_requesterexplainrequiresobjectandoperation;userIdalone answers400 VALIDATION_FAILED@objectstack/spec@17.4.0ExplainRequestSchema:object: z.ZodString,operation: z.ZodEnum, both non-optional,userId: z.ZodOptionalpositionis stored verbatim, matches nothing, grants no settextcolumn with no reference resolution cannot refuse an id; the bindings look positions up byname); the runtime half —201, no diagnostic, noclm_legal— is #50's measurement, handed down with this dispatch. Flagged in the report as the one claim not measured first-hand here.objectstack-ai/objectstack#16712Gates
Exit codes captured before any pipe (each gate redirected to a file,
$?read immediately after):Re-run on
0a9c340after the rework, same method. Byte-identical verdicts to the run on6a86e41— same 21 warnings, same 5 suggestions, same 1541 keys and 0 missing — which is the measurement behind "a prose diff cannot move them".None of them moved, and none of them could:
tsconfig.jsonincludes onlyobjectstack.config.tsandsrc/**/*,validate/lintwalk the stack metadata graph, andscripts/check-lint-i18n-gate.mjsnever readsREADME.md. The lint warnings above are the tree's pre-existing carrier-only-field and empty-approver-slate notes, untouched by a prose diff.Browser
No browser run, and none is owed. The diff changes no runnable surface — no object, view, page, app, flow or action — so there is nothing to drive. AGENTS.md's browser rule is scoped to cards that change a surface a human touches; this one changes only prose about surfaces that already exist.
Rework — the
_id-suffix tell was false, and is goneThe first push claimed
positionwas "the one field on that row without an_idsuffix". Enumerating every field ofSysUserPositionin@objectstack/plugin-security@17.4.0shows that is false twice over: fourteen fields, only three of which carry the suffix, andgranted_by,delegated_fromandcertified_byare eachField.lookup("sys_user")with no suffix at all. The sentence taught a tell that its own object falsifies three times — an operator generalising it ontogranted_bywould spell a name into a genuine lookup, which is the mirror image of the bug this subsection exists to prevent. It was the card's own failure mode, reintroduced in a new sentence, and it was mine: I had verifiedposition,user_idandpermission_set_idindividually and carried the "one field" framing over without enumerating the row.0a9c340rebuilds that one paragraph on the evidence that does hold — the field's own declaration (text, description "Position machine name (referencessys_position.name)") — and says outright that the field name tells you nothing here because the object mixes both kinds. Thesys_user_permission_set.permission_set_idcontrast stays as it was: that one really is id-typed, which is exactly what makes the wrong guess tempting. Nothing else in the diff moved — no restructuring, no new sections, Edit 1 and #55's tables untouched.Acceptance notes
Two observations found while verifying, neither filed and neither touched here:
allFlowsinsrc/flows/index.tsregisters 12 (SignatureRecordOnCreateFlowandRenewalStartFloware the pair that makes the count read as ten files). Nothing in the README states a flow count, so the new status line names the reminder layer without a number.(business requesters)— a default permission set, not a position — while the seventh real position,clm_admin(src/sharing/positions.ts), is absent from the table.DESIGN.md§11 disagrees with itself on the same count (the layout block says "7 position", the M1 row says "8 position / 6 set"). Left alone: it is The demo makes M3's reminder layer look broken: 54 of 120 contracts have nolegal_owner, so four of six scheduled jobs notify nobody #55's table, Edit 2 does not require touching it, andDESIGN.mdis off this card's file surface. The PM has confirmed it and is filing it as its own card.Generated by Claude Code