| M1-52/1 |
2026-10-14 |
Base int/m1-wave14 at fd504d9, branch feat/M1-52-chat-spaces in its own worktree; the branch is not merged locally, the integration lane ports it. The migrations take the next free numbers of the base, 00023 and 00024. Text this ticket adds is English. |
Instructions of the launcher, which override the brief's merge step and language (as M1-51/1). |
| M1-52/2 |
2026-10-14 |
Fractional index scheme (chat/domain/fracindex): the widely used base-62 scheme with integer part; digits 0-9A-Za-z in ASCII order, head letters a to z for 1 to 26 integer digits and A to Z for 26 to 1 (negative range), no fraction ending in 0, A with 26 zeros invalid. KeyBetween("", "") is a0, an open upper end increments the integer part, an open lower end decrements it, otherwise the shared prefix plus the rounded-up digit midpoint. Lists order by sort_key COLLATE "C", then id. |
The brief's scheme and vectors; byte order equals key order, appending keeps keys short (10000 appends stay at four characters). |
| M1-52/3 |
2026-10-14 |
Lock for create and move: first the transaction advisory lock 0x626f746879737063 (a fixed value of its own), then every space that is not archived in ascending id (FOR UPDATE), then the neighbours are read in a new statement and the key computed. Archive and update lock only their row. |
With no space that is not archived there is no row to lock, and two first creates computed the same key a0 (TestConcurrentFirstCreatesKeepDistinctKeys fails without the advisory lock). Read committed sees the rows a waiting writer committed in the next statement, so later keys stay distinct (TestConcurrentMovesKeepDistinctKeys). |
| M1-52/4 |
2026-10-14 |
sidebar.updated carries no resource.version; the payload SidebarPayload carries the version of the personal row. The scope is the space. A repeated identical PUT answers the same state and still counts the row version up (last write wins). |
04 section 2.2 rule 3 names sidebar.updated among the types without resource.version, and the catalogue says the same; the brief's "resource version = state version" contradicts it. |
| M1-52/5 |
2026-10-14 |
Structure.ListSidebarStates(ctx, a actor.Actor) takes the caller instead of a user id. |
Visibility runs through authorize, which needs the credentials of the caller (owner role only in a session, SR5). |
| M1-52/6 |
2026-10-14 |
authz: Effective caps the level at read on an archived space path as on an archived channel path (one function, archivedCap); resolveSpace reads SpaceArchived. The rights views carry the level on each space itself (AccessView.SpaceLevels), and DiffVisible reports a space whose own level changes in changed with level and previous_level, for every rights change, not only for archiving. The new port authz.RightsQuery (EffectiveRights, implemented by the Authorizer on the same snapshot and resolution as authorize) gives the level and capabilities of SpaceView; a space visible only through a channel reports read without capabilities. |
07 section 4.5 point 3 asks for a changed entry at read for a space without channels, which the views of M1-29 could not express; spaces in the view only appeared or disappeared before. |
| M1-52/7 |
2026-10-14 |
Order of the checks: updateSpace authorizes, locks, refuses an archived space with 409, then checks If-Match and the body; moveSpace authorizes space.reorder on the instance, refuses both fields (/before_id), locks the order, answers an unknown space 404 and an archived one 409, then checks the target (/after_id or /before_id: itself, unknown or archived); putMySidebarSpace refuses agents and apps with 403 forbidden (reason subject_kind_not_allowed) before any lookup, then authorizes sidebar.update (404 for an invisible space); an archived but readable space keeps its sidebar row writable. |
The brief's codes; the kind check reveals nothing about a space. |
| M1-52/8 |
2026-10-14 |
grants_target_space_id_fkey is added NOT VALID in 00023 and validated in 00024. bothy_app may read, insert and update spaces (never delete: archiving is final) and read, insert, update and delete user_space_states (personal data). Test fixtures of authz and identity that put a space grant into the database now insert the spaces row first. |
upgrade.md rule M5; 03 spaces retention class content. |
| M1-52/9 |
2026-10-14 |
docs/api/openapi.yaml: the examples of the space operations use icon keys of the design system (headset, flask) instead of icon.headset and icon.flask, and moveSpace declares 409. |
An example must pass the rule it documents; moving an archived space is 409 (07 section 4.5 point 2). |
| M1-52/10 |
2026-10-14 |
Field codes: /name required, too_long or the textcheck rule, taken with 409 conflict; /color, /icon, /sort_key, /after_space_id, /after_id, /before_id invalid; /use_global_order not_allowed; /reason required or the textcheck rule. Audit details: none for space.create and space.reorder beyond the resource, changed_fields for space.update, reason for space.archive, no_change: true for an update without change and for archiving an archived space; sidebar.update is not audited (05). |
Ids and codes only (SEC-AUD-5). |
| M1-52/11 |
2026-10-14 |
Files outside the brief's scope that the ticket had to change: the space fixtures of the authz and identity tests (foreign key), api/routes/real_test.go (the enabled table and the stub server), the configuration tests, tools/configdoc/planned.yaml (the rule space. is stale once the key is registered) and the generated configuration reference, and wire/empty_sources.go, whose structure placeholder keeps only the channel and thread part. |
Enabling operations and registering a key need these (M1-48/12). |
| M1-52/12 |
2026-10-14 |
Test first held for fracindex, the domain checks, the authz changes and the migration (each red run failed to build or failed before the code); the HTTP tests over PostgreSQL were written after the use cases and adapters. Two mutations show they bite: without the space archive cap TestSpaceArchiveIsRightsChange finds no access.changed, and without the advisory lock TestConcurrentFirstCreatesKeepDistinctKeys finds the duplicate key a0. |
Honest record of the order of work. |
| M1-52/13 |
2026-10-14 |
fracindex.Valid refuses only the bare smallest integer (A with 26 zeros), not that integer with a fraction, as the reference scheme does; KeyBetween("", k) below such a key appends a fraction. Unit tests over in-memory fakes cover the use cases (app), the repository runs against PostgreSQL in its own package, and the structure source has its own test, so the server coverage stays above quality.coverage.go_min. |
The first full suite measured 78.7 % because coverage counts per package and only adapters/httpapi had tests; the vector test of the smallest integer showed the stricter check. |
| M1-52/14 |
2026-10-15 |
Port onto main: code and tests are taken byte for byte from the integration squash, with INT-W15/2 (the chat source implements SpaceDetails, emptyChannels implements ChannelDetails, the chat HTTP test world passes ChannelDetails to its channel fake, and the authz HTTP test world writes the spaces row in the loop that puts the space details of M1-49) and the part of INT-W15/1 that concerns this ticket (the append-only files keep both sides). The migrations keep the numbers 00023_chat_spaces.sql and 00024_grants_space_fk_validate.sql of the integration branch, which follow 00022_agents.sql without a gap; INT-W15/3 to /5 concern M2-39, which is not on main. The later entries removed from api/operations.yaml use the key milestone of main. The code comments, the SQL comments and the comments of 00023_chat_spaces.sql name the English chapter files and headings (02-architecture.md, 03-data-model.md, 05-actions.md, 03 principle 5 and "Configuration values of this chapter"), so its checksum is recorded anew, and space.name_max_chars names the chapter 03-data-model.md. The changelog entry, the module README (status line, code list and the new section on the use cases) and the key row of docs/operations/configuration.md are written into the English text at the same places. M1-52/7 and the module README write the refusal of agents and apps as 403 forbidden (reason subject_kind_not_allowed), the form of the problem code check of main, and the two non-ASCII test inputs of domain_test.go and fracindex_test.go stand between language-check: off and language-check: on comment lines for the language gate (D-W-94). The main-only case TestEmptySourcesKnowNothing of M1-29 now checks emptyChannels (with ChannelDetails) instead of emptyStructure, which this ticket replaces because chat is now the space source. Generated files are regenerated, not merged. |
main is English; the integration fix belongs to the ticket that replaces the empty structure (INT-W15/2); the case was added on main after the integration base. |