feat: declare rust as the module's identity capability (phase 5, D16)
#5
22
README.md
22
README.md
@@ -59,6 +59,28 @@ events, the live map, Discord commands — arrives phase by phase. **Nothing is
|
||||
has something behind it:** a declared trigger nothing emits and a declared slot nothing fills are
|
||||
both surfaces an operator can configure and then wait on, which is worse than an absent one.
|
||||
|
||||
### What a client feature-detects on
|
||||
|
||||
`module.json` declares six capability strings, and `GET /api/v1/public/modules` hands them to any
|
||||
client that asks — the website's own nav, and the Android app (`docs/modules/rust/PLAN.md` R10).
|
||||
Five of them name a surface: `servers`, `killfeed`, `leaderboard`, `presence`, `wipes`.
|
||||
|
||||
The sixth is `rust`, and it names **the module itself**. It looks redundant beside `id`, and it is
|
||||
not, for two reasons worth writing down before somebody tidies it away:
|
||||
|
||||
- **A client that asks "is this module installed" has nowhere else to ask.** Core flattens every
|
||||
started module's capabilities into one list, so `servers` alone is a word another module could
|
||||
declare tomorrow and silently reveal this one's screens. `rust` is the string that can only mean
|
||||
this module, and it is the single gate a whole navigation group hangs on — exactly the job `shard`
|
||||
does for `module-uo`.
|
||||
- **`id` answers a different question.** It is a *mount prefix* (§2.1 requires it to equal the
|
||||
directory core loads the module from), and `MODULE_API.md` §2.9 is explicit that a client must
|
||||
never infer a route from a capability. Gating on `id` would quietly make the two the same thing,
|
||||
and the day a client builds `/<id>/servers` from it, the contract that lets this module move its
|
||||
own pages is gone.
|
||||
|
||||
An unknown capability is absent, and no route is ever derived from one.
|
||||
|
||||
## Build and check
|
||||
|
||||
```bash
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"admin": ["/rust"],
|
||||
"player": ["/rust"]
|
||||
},
|
||||
"capabilities": ["servers", "killfeed", "leaderboard", "presence", "wipes"]
|
||||
"capabilities": ["rust", "servers", "killfeed", "leaderboard", "presence", "wipes"]
|
||||
}
|
||||
|
||||
@@ -148,3 +148,20 @@ test('the module’s protocol version agrees with the manifest it ships beside',
|
||||
assert.strictEqual(typeof sidecar.PROTOCOL_VERSION, 'number')
|
||||
assert.ok(sidecar.PROTOCOL_VERSION >= 1)
|
||||
})
|
||||
|
||||
test('an identity capability is declared, and it is the module id (phase 5, D16)', () => {
|
||||
// Core flattens every started module's capabilities into ONE list, so a client
|
||||
// asking "is this module installed" needs a string only this module can
|
||||
// declare. `servers` is not that string — it names a surface, and another
|
||||
// module could name it too — which is the whole reason this one exists beside
|
||||
// the five surface words.
|
||||
//
|
||||
// It is asserted against `manifest.id` rather than against the literal "rust"
|
||||
// so that the two cannot drift: the day the id changes, the capability a
|
||||
// client gates a whole navigation group on has to change with it.
|
||||
assert.ok(
|
||||
manifest.capabilities.includes(manifest.id),
|
||||
`module.json must declare "${manifest.id}" as a capability — it is the only string a client can` +
|
||||
' use to tell this module apart from any other, and the Android app gates its Rust rows on it',
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user