feat(teams): the roster's audience projection, and optionalAuth to resolve it
TEAMS.md §3.3, as the eighth member of MODULE_API 1.6.0 — amended in place per the org lead, on the rule Protocol 4 was given in phase 2: a contract owes a bump only once it has landed on `main`. Two questions meet on the roster and they belong to different owners. WHICH ROWS a viewer may see is the module's, because the audience rungs and their configuration live there and core does not know what a rung is. WHAT A ROW LOOKS LIKE stays core's. So `projectRoster` answers with member KEYS, not rows. §3.3 said rows, and rows would let a module widen what is published — handing back a `userId` core had withheld — leaving core's field guarantee resting on every module's good behaviour. Core asks which rows and re-normalises the answer through its own public shape, so a module can narrow and cannot widen. "The module declines" needed splitting before it could be implemented. No module at all and a module whose rungs could not be consulted are opposite situations: the first withholds nothing and must serve the roster whole, the second must serve none of it. The refusal carries `projects`, and only `projects: true` fails closed. Without the split, bare core serves an empty roster on every Team page. This is also the first public route whose CONTENT depends on identity, which needed a middleware core did not have. `attachSession` only decodes a token, so a banned account, a password change or a logout would have kept working against the private half of a feed until the JWT expired. `optionalAuth` runs requireAuth's full database re-validation and, on any failure, continues ANONYMOUSLY rather than rejecting — a caller whose session is no longer good sees the public view, which is what they are entitled to. `GET /public/teams/:slug/activity` lands here for the same reason: §2.11's route table had no activity endpoint though §4.3 describes a filtered feed. Paged, with the visibility resolved from the session and never from a parameter. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -243,11 +243,22 @@ function checkLegShape(entry) {
|
||||
return { leg, label: label || leg, dispatch, classify }
|
||||
}
|
||||
|
||||
// All three methods are REQUIRED, with no optional half. A provider that could
|
||||
// list Teams but not their members would leave core holding Teams it can never
|
||||
// Three methods are REQUIRED, with no optional half. A provider that could list
|
||||
// Teams but not their members would leave core holding Teams it can never
|
||||
// populate, and the reconciler has no sensible behaviour for that — it is not the
|
||||
// same as a call that fails, which is staleness and already handled (§2.4). A
|
||||
// module unable to answer one of the three answers `{ ok: false }` at call time.
|
||||
//
|
||||
// `projectRoster` is the fourth and is OPTIONAL (TEAMS.md §3.3): it expresses an
|
||||
// audience model, and a module with no rung system of its own has no opinion to
|
||||
// express. Omitting it means core serves rosters at its own public shape;
|
||||
// implementing it means core fails CLOSED when the call cannot be made, so this
|
||||
// is a member to add deliberately rather than by habit.
|
||||
//
|
||||
// The copy is explicit rather than a spread: this object is what core calls, so
|
||||
// anything not named here is not part of the contract and must not survive
|
||||
// registration. A method that silently rode along would look implemented from the
|
||||
// module's side and be invisible from core's.
|
||||
function checkTeamProviderShape(entry) {
|
||||
const provider = entry || {}
|
||||
const out = {}
|
||||
@@ -257,6 +268,12 @@ function checkTeamProviderShape(entry) {
|
||||
}
|
||||
out[name] = provider[name]
|
||||
}
|
||||
if (provider.projectRoster !== undefined) {
|
||||
if (typeof provider.projectRoster !== 'function') {
|
||||
throw new Error('registerTeamProvider: projectRoster must be a function if present')
|
||||
}
|
||||
out.projectRoster = provider.projectRoster
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user