@niscorp/nisc 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +76 -0
  3. package/package.json +10 -10
package/AGENTS.md CHANGED
@@ -90,14 +90,14 @@ Pick per need; every piece works standalone. Nova is the only mandatory one for
90
90
  6. Nova doesn't know Prism: the shell's injected `transform` is the socket that makes Prism endpoint configs work. Wire it once in setup, and fold ambient app context (the app's "today", the session's principal) into the source there — per session on the server under moss, in the shell factory in a client-degrade app — never into per-action data. The socket takes Prism's `evaluate` as it is (`transform: evaluate`), or a wrapper that adds to the source: `(config, source) => evaluate(config, { ...source, today })`. Hand `evaluate` the config object itself, never a copy of it and never one parsed again: it checks a config the first time it sees the object and only evaluates it after that. A test or a tool that must check everything on every call passes `{ check: 'always' }`. A config that is stored is compiled once and its IR executed (`compile`, `execute`).
91
91
  7. An endpoint is a contract, not an implementation (D1, D2). In-browser, server, or mixed — actions can't tell the difference, and must not be able to. Server functions are endpoints too: the manifest's `functions(session)` seam runs them in-process under the caller's policy. The seam returns a list of `{ id, description, execute }`: the id is what the charter and an `fn:` endpoint name, and the description says what the function does and what to send, for the model that writes a call to it; a map of handlers is deprecated. A client-side `fn` registry is the escape hatch for client-degrade apps, and discouraged elsewhere.
92
92
  7a. The manifest's three code seams are distinct, and app code goes in the one that describes it. `functions(session)` is for **endpoints** — things an action calls. `onSession(session)` is for per-shell code that is **not** an endpoint: an observer, a roster, an agent watching the screen. `runs` is the **sink** for model runs, fed by `session.recordRun`. Registering a handler nothing calls, to get code to run per session, is a lie about what that code is — use `onSession`. Both it and `functions` run before the shell finishes building, so neither may touch `session.shell` synchronously. The members of that shell that change what it is made of (`registerAction`, `setLayout`, `addCanvas` and their kin) throw there; the whole shell is `unverifiedShell(session)` from `@niscorp/moss/unverified`.
93
- 7b. A layout generated while the app runs is saved through `session.artifacts`, not set on the shell. `session.artifacts.save([{ kind: 'layout', body: { ref: 'action/<the action’s id>', layout, use: true } }])` keeps it for the session's principal: it is theirs alone, it is drawn on their screens at once with their data kept, and every save is a version (`list`, `history`, `restore`, `reset`). Without `use: true` it is kept and not drawn. The layout of the app's own shell is saved the same way under `ref: 'frame'`, and a canvas's under `ref: 'canvas/<the canvas’s id>'`: a page keeps the layouts it declares, a shell manifest with no `layout` (or a canvas with no `actionLayout`) has none to take the place of, and a frame that leaves a canvas out is saved as it is, so check that yourself where it matters. A canvas their shell does not declare, that a layout in force for a person has a canvas slot for, is taken out of their shell, with what is mounted on it, once no layout has the slot: declare a canvas that is to stay. No call can name anybody else: moss reads and writes those rows as the caller, and vex keeps them to the caller's own. **Who may save is the charter's**: put the call in a function, and grant the action that calls it to the roles that may. A body moss refuses (a ref no action of theirs keeps a layout under, a component no shell has, a binding to data the action lacks, a control the action listens on left out) rejects with a sentence: hand it back to whoever wrote the layout. A layout for everybody is published from the app's own server code and never from a session: `server.defaults(publisher).save([...])`, with the same body and the same five calls. It is drawn for everybody who holds the action, under a person's own layout in use and their roles' layout variant, and over the build's. `publisher` is who published, kept with the save: hand it the session's principal. **Who may publish is the charter's** too: put the call in a function and grant the action that calls it to the owners' role. No `userId` is `*`: that is whose the published rows are. An action or a fragment generated while the app runs is saved the same way: `{ kind: 'action', body }` or `{ kind: 'fragment', body }`, the body without an `id`, because moss gives it (`generated.` and a uuid, which `save` answers). It is its owner's alone, and what it defines replays reads and calls the functions its owner's roles grant: each endpoint is a `POST` to the app's vex route naming one fingerprint of a read the app ships and its owner may replay, or of a read vex wrote that its owner saved, or an `fn:` endpoint to a function its owner's roles grant (`session.functions()` lists them with their descriptions: hand that list to the model that writes the action). A read vex wrote for `session.answerIntent` is kept the same way: `session.artifacts.save([{ kind: 'read', id: '<the fingerprint vex answered>' }])`, with no body. It is answered to that person alone, it survives a schema change that does not touch what it reads, and their generated actions may replay it. A function its owner's roles do not grant, a write and any other address are refused with a sentence, and so is a generated action that opens an action its owner does not hold. Once it is in the shell it is an action like any other: it emits and listens on channels, and a fragment's steps run inside the action it is wrapped around. What an assistant may generate is the app's to decide, in its prompt and in the function that calls `save`. It is in its owner's shells a moment after `save` answers, not before. What is still being generated, and is to be seen at once, goes into the draft of the one shell it is worked on in: `session.draft.put([...])` takes what `save` takes and answers when that shell has it, so the function opens it on its next line; `session.draft.save()` then keeps all of it in one save, and `discard()` drops it. A draft is saved nowhere and is gone when its shell is. Never give an action or a fragment the app ships an id beginning with `generated.`. A layout is the one kind that is published.
93
+ 7b. A layout generated while the app runs is saved through `session.artifacts`, not set on the shell. `session.artifacts.save([{ kind: 'layout', body: { ref: 'action/<the action’s id>', layout, use: true } }])` keeps it for the session's principal: it is theirs alone, it is drawn on their screens at once with their data kept, and every save is a version (`list`, `history`, `restore`, `reset`). Without `use: true` it is kept and not drawn. The layout of the app's own shell is saved the same way under `ref: 'frame'`, and a canvas's under `ref: 'canvas/<the canvas’s id>'`: a page keeps the layouts it declares, a shell manifest with no `layout` (or a canvas with no `actionLayout`) has none to take the place of, and a frame that leaves a canvas out is saved as it is, so check that yourself where it matters. A canvas their shell does not declare, that a layout in force for a person has a canvas slot for, is taken out of their shell, with what is mounted on it, once no layout has the slot: declare a canvas that is to stay. No call can name anybody else: moss reads and writes those rows as the caller, and vex keeps them to the caller's own. **Who may save is the charter's**: put the call in a function, and grant the action that calls it to the roles that may. A body moss refuses (a ref no action of theirs keeps a layout under, a component no shell has, a binding to data the action lacks, a control the action listens on left out) rejects with a sentence: hand it back to whoever wrote the layout. With `runtime.artifactsUnusedMs`, what a person saved and nobody has loaded for that long is deleted, every version of it; what is published never ages. What is for everybody is published from the app's own server code and never from a session: `server.defaults(publisher).save([...])`, with the same kinds, the same bodies and the same five calls. A published layout is drawn for everybody who holds the action, under a person's own layout in use and their roles' layout variant, and over the build's. A published action or fragment is given its id by whoever publishes it (`{ kind: 'action', id: 'promo', body }`): the charter grants it by that id, and one under a shipped id replaces the build's for everybody until it is reset. A published read is answered to everybody under its fingerprint. `publisher` is who published, kept with the save: hand it the session's principal. **Who may publish is the charter's** too: put the call in a function and grant the action that calls it to the owners' role. No `userId` is `*`: that is whose the published rows are. An action or a fragment generated while the app runs is saved the same way: `{ kind: 'action', body }` or `{ kind: 'fragment', body }`, the body without an `id`, because moss gives it (`generated.` and a uuid, which `save` answers). It is its owner's alone, and what it defines replays reads and calls the functions its owner's roles grant: each endpoint is a `POST` to the app's vex route naming one fingerprint of a read the app ships and its owner may replay, or of a read vex wrote that its owner saved, or an `fn:` endpoint to a function its owner's roles grant (`session.functions()` lists them with their descriptions: hand that list to the model that writes the action). A read vex wrote for `session.answerIntent` is kept the same way: `session.artifacts.save([{ kind: 'read', id: '<the fingerprint vex answered>' }])`, with no body. It is answered to that person alone, it survives a schema change that does not touch what it reads, and their generated actions may replay it. `session.answerIntent` also replays a read by its fingerprint, `{ fingerprint, context }`, and answers it with what vex knows of it (the context it binds, what the call left out, its warnings), where the wire answers the rows alone. A function its owner's roles do not grant, a write and any other address are refused with a sentence, and so is a generated action that opens an action its owner does not hold. Once it is in the shell it is an action like any other: it emits and listens on channels, and a fragment's steps run inside the action it is wrapped around. What an assistant may generate is the app's to decide, in its prompt and in the function that calls `save`. It is in its owner's shells a moment after `save` answers, not before. What is still being generated, and is to be seen at once, goes into the draft of the one shell it is worked on in: `session.draft.add([...])` takes what `save` takes and answers when that shell has it, so the function opens it on its next line; `session.draft.save()` then keeps all of it in one save, and `discard()` drops it. A draft is saved nowhere and is gone when its shell is. Never give an action or a fragment the app ships an id beginning with `generated.`.
94
94
  8. Writes are endpoints fired by triggers, never inline mutations (D4). Identity and tenancy are stamped server-side from the session, never client-supplied: a form never carries `owner_id`. What a principal *is* beyond its id — a tenant, an org, a region — is the manifest's `scope(principal)` seam: moss always injects `{ userId }`, `scope` contributes the rest, and the merged set is what a behavior's `to:` resolves against at execute. The mapping is application knowledge, so the app supplies it; the values are injected server-side and unreferenceable by a request.
95
95
  9. Formatting and derivation live in Prism transforms, never in components and never in action code.
96
96
  9a. File pickers live in the kit. A file never goes over the socket; the app saves it wherever it wants.
97
97
 
98
98
  **Principals**
99
99
 
100
- 10. The charter is the app's policy document: role names → glob selections over the app's universes — `actions` (which action ids exist for a principal), `data` (`table.verb` capabilities, compiled into the Vex `ScopePolicy`) and `functions` (which of the app's functions a principal's shell holds: a role with none may call none). Grant each role the functions its actions call, deliberately: moss refuses to boot and lists every call whose grants miss, for each role on its own, each combination people wear and `public`, and says where a role allows every function (`'*'`). The charter compiles; it never enforces and never shapes. Moss resolves it per principal and refuses to boot incoherent. Assignments (principal → roles) are app data beside it; row-level semantics the charter can't express are `behaviors`, compiled into the policy. One role name is fixed: a request with no session, and a signed-in principal with no assignment, resolve to `public`, so every charter defines it — `public: []` where it grants nothing. Boot does not check for it; without it those requests fail (`Unknown role "public"`).
100
+ 10. The charter is the app's policy document: role names → glob selections over the app's universes — `actions` (which action ids exist for a principal), `data` (`table.verb` capabilities, compiled into the Vex `ScopePolicy`) and `functions` (which of the app's functions a principal's shell holds: a role with none may call none). Grant each role the functions its actions call, deliberately: moss refuses to boot and lists every call whose grants miss, for each role on its own, each combination people wear and `public`, and says where a role allows every function (`'*'`); `server.checkFunctions()`, called once the app holds the server and has mounted every route of its own, before it listens (nobody's shell makes its reads in-process, and the server takes no route once it has answered one), builds nobody's shell once to read the functions and refuses every call to one the app does not have. The charter compiles; it never enforces and never shapes. Moss resolves it per principal and refuses to boot incoherent. Assignments (principal → roles) are app data beside it; row-level semantics the charter can't express are `behaviors`, compiled into the policy. One role name is fixed: a request with no session, and a signed-in principal with no assignment, resolve to `public`, so every charter defines it — `public: []` where it grants nothing. Boot does not check for it; without it those requests fail (`Unknown role "public"`).
101
101
  10a. The `data` universe is the INTROSPECTED database, not your schema file: it holds the engine's own tables too — moss's `integrations` (integration key hashes), `integration_actions`, `moss_generation`, `sessions` (session token hashes, under moss's credential), the `strata_ledger`, the vex cache. A role granted a data wildcard (`*.read`) denies them explicitly (`data: { allow: ['*.read'], deny: [...] }`; denies do not inherit, so every role that grants the wildcard denies). A charter check resolves against the universe boot resolves against — the app's migrations and moss's run on a scratch database, then introspected — or it proves nothing about wildcards.
102
102
  11. Per-principal UI is existence or a served variant, never a conditional. An action a principal lacks does not exist in their shell (ring 1); a different shape of the same action is a served variant (ring 2). Layouts and components never branch on roles or capability data. Ring 1 does the deriving: a canvas's `initial` takes a **candidate list** and the first id the principal actually holds mounts — members boot their home, anonymous boots the login, and nobody configures which. An ungranted candidate simply isn't there.
103
103
  11a. Per-principal boot has two hooks, and they are twins. `inputs` derives boot **data** — merged over each canvas's static seed, read from action data downstream. `seeds` derives boot **instances** — which actions to push onto which canvases, computed from the session and its own reads over the session's wire, ring-1-filtered like every other mount. A composed surface (a home, an agent's column) is `seeds` plus a `list` canvas; hand-authoring the same arrangement names it twice. Branching chrome on `inputs` is a stopgap where a served variant doesn't exist yet.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,81 @@
1
1
  # @niscorp/nisc
2
2
 
3
+ ## 0.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 8eaea60: The docs say where `server.checkFunctions()` goes: after every route of the app's own, before it listens.
8
+
9
+ Nobody's shell makes its reads in-process, and the server takes no route once it has answered one: a route mounted after the check throws `Can not add a route since the matcher is already built.` moss's DOCS, nisc's AGENTS and the cli README say so.
10
+
11
+ - 1a67306: `nisc start` checks the app's functions after the project's `routes` and the site are mounted.
12
+
13
+ Checking the functions answers requests in-process, and the server takes no route once it has answered one. An app with functions whose first screen reads as it mounts failed to start: `Can not add a route since the matcher is already built.`
14
+
15
+ - Updated dependencies [8eaea60]
16
+ - Updated dependencies [1a67306]
17
+ - @niscorp/moss@0.8.1
18
+ - @niscorp/cli@0.8.1
19
+
20
+ ## 0.8.0
21
+
22
+ ### Minor Changes
23
+
24
+ - 2076f80: `session.draft.add` is what `session.draft.put` was. **Breaking.**
25
+
26
+ BREAKING — approved by moccadroid, 2026-10-18: rename `session.draft.put(...)` to `session.draft.add(...)`. Nothing else about it changes: it takes what `save` takes, what is added is in that one shell when the add answers, and `save`, `discard`, `show`, `remove` and `list` are as they were.
27
+
28
+ - cdc89cc: `server.checkFunctions()` reads the app's functions and refuses what is wrong with them before anyone is served. **Breaking.**
29
+
30
+ BREAKING — approved by moccadroid, 2026-10-18: `serve` of `@niscorp/moss/node`, and `nisc dev`, `nisc build`, `nisc check` and `nisc start`, refuse to come up on an action that calls a function the app has no function of, a charter `functions` deny that matches nothing, or two functions under one id. An app that hands a function only to signed-in sessions, with a shipped action calling it, hands it to every session (the function refuses whom it must), or is refused.
31
+
32
+ An app's functions are known only once a session is: `functions(session)` is called for each shell, and a session hook may reach for the server object, which the app holds only once `createServer` has answered. So the check is the app's to call, once it holds the server and before it listens; `serve` of `@niscorp/moss/node` calls it, and so do `nisc dev`, `nisc build`, `nisc check` and `nisc start` after the project's `boot`; each closes what it built where it refuses. It builds nobody's shell once, after everything is in force, and drops it. What that call returned is checked, and it refuses on:
33
+ - **an action, or a fragment a shell ships, that calls a function the app does not have**: `"home" calls "auth.leave", which the app has no function of`. Before, the call failed when a person pressed the button. An app with no `functions` has none. A set of actions handed over while the app runs is held to the same at that refresh.
34
+ - **a charter `functions` deny that matches nothing**: `Charter is incoherent over the app’s functions — refusing to serve:`.
35
+ - **two functions under one id**: `moss: functions(session) returned "auth.leave" twice.`
36
+
37
+ What the charter only warns of (a grant that names no function, a function no role grants), the functions with no description, and a map of handlers are said at that point, once.
38
+
39
+ The cost is one anonymous shell built, as any visitor who is not signed in causes. Every check reads nobody's functions: an app that hands a function only to signed-in sessions is refused where an action calls it.
40
+
41
+ ### Patch Changes
42
+
43
+ - edd3271: `session.answerIntent(writers, asked)` also replays a query that exists: `asked` may be `{ fingerprint, context }`.
44
+
45
+ It is answered as the caller, as an intent is, with what vex knows of the query: the context keys it binds, the ones the call left out (`meta.missingContext`), its warnings. The wire answers a replay with the rows alone. A model that proves a query before it builds on it learns from this why a replay came back empty.
46
+
47
+ - f461b98: What a person generated and nobody has used for long enough is deleted, with `runtime.artifactsUnusedMs`.
48
+ - A version of an artifact a person saved is stamped as used (`artifacts.last_used_at`, migration `nisc.moss/6`) when a shell is built for them, once an hour at most, through the same vex entries and row rule as the save itself.
49
+ - A sweep runs as the server comes up and then every hour: every artifact of a person whose newest version was neither saved nor used within `artifactsUnusedMs` is deleted, every version of it, and the saves left empty with them. What is published for everybody (`*`) never ages; what the build ships is not a row.
50
+ - Without the setting nothing is deleted. `0` or less refuses the boot.
51
+ - The sweep is one raw statement in moss (`data/aging.ts`), entered in the raw-SQL ledger: vex keeps every statement of its own to one principal's rows, and the sweep crosses them all.
52
+
53
+ - 37422bf: `server.defaults(publisher)` publishes every kind a person saves: an action, a fragment and a read, beside a layout.
54
+ - **An action or a fragment is given its id by whoever publishes it**, as a shipped one has one: `server.defaults(who).save([{ kind: 'action', id: 'promo', body }])`. One without an id is refused (`moss: a published action is given its id by whoever publishes it: name it.`), and so is one under `generated.`. The charter grants it by that id: a new id needs a grant, and one that is a shipped action's or fragment's replaces the build's for everybody until it is reset.
55
+ - **A read vex wrote is published under its fingerprint**, `save([{ kind: 'read', id: '<fingerprint>' }])`, and is answered to everybody, nobody signed in included, after what the caller saved themselves. One under the fingerprint of a shipped entry is refused. A generated action anybody saves may replay it.
56
+ - **Checked as a person's save is, as the publisher, and then as a manifest.** An action's `fn:` calls are ones the publisher's roles grant and its reads ones they may replay; then the whole set in force is verified as a set handed over is, and a holder whose roles do not grant a call refuses the publish: `Publish refused — actions call functions the app does not have, or that their holders’ charter does not grant:`.
57
+ - **A default that no longer passes under a new build is left out, and the build's copies stand**, with a line said once per read of the rows: `[moss] what is published for everybody is left out, and the build’s copies stand: …`.
58
+ - `publishedArtifacts(saved, publisher)` of the running manifest takes who publishes, for the roles and reads a publish is held to; `server.defaults` resolves them from the publisher's principal.
59
+
60
+ - Updated dependencies [edd3271]
61
+ - Updated dependencies [f461b98]
62
+ - Updated dependencies [2076f80]
63
+ - Updated dependencies [cdc89cc]
64
+ - Updated dependencies [37422bf]
65
+ - Updated dependencies [63b9ab5]
66
+ - Updated dependencies [6a62604]
67
+ - Updated dependencies [c952ab0]
68
+ - Updated dependencies [6680ad6]
69
+ - Updated dependencies [6680ad6]
70
+ - Updated dependencies [1c78d6d]
71
+ - @niscorp/moss@0.8.0
72
+ - @niscorp/cli@0.8.0
73
+ - @niscorp/nova@0.5.0
74
+ - @niscorp/loom@0.5.0
75
+ - @niscorp/signal@0.1.5
76
+ - @niscorp/vex@0.4.1
77
+ - @niscorp/cortex@0.1.4
78
+
3
79
  ## 0.7.0
4
80
 
5
81
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@niscorp/nisc",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "The nisc platform as one known-compatible set: every @niscorp package, each pinned to the exact version it was released with.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -19,18 +19,18 @@
19
19
  "node": ">=22.12"
20
20
  },
21
21
  "dependencies": {
22
- "@niscorp/cli": "0.7.0",
23
- "@niscorp/loom": "0.4.0",
24
- "@niscorp/cortex": "0.1.4",
25
- "@niscorp/moss": "0.7.0",
26
22
  "@niscorp/charter": "0.1.4",
27
- "@niscorp/nova": "0.4.0",
23
+ "@niscorp/cli": "0.8.1",
24
+ "@niscorp/cortex": "0.1.4",
25
+ "@niscorp/moss": "0.8.1",
26
+ "@niscorp/loom": "0.5.0",
27
+ "@niscorp/nova": "0.5.0",
28
28
  "@niscorp/prism": "0.3.1",
29
- "@niscorp/signal": "0.1.4",
29
+ "@niscorp/signal": "0.1.5",
30
30
  "@niscorp/solid": "0.1.2",
31
- "@niscorp/strata": "0.1.7",
32
- "@niscorp/vex": "0.4.0",
33
- "@niscorp/tide": "0.1.7"
31
+ "@niscorp/tide": "0.1.7",
32
+ "@niscorp/vex": "0.4.1",
33
+ "@niscorp/strata": "0.1.7"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "zod": "^4.2.0"