chore: scrub simulation vocabulary from app comments + settle doc-debt
Enforce the boundary in code-comments and doctrine (adversarial-review cleanup): - App comments in the data plane no longer narrate the SDK's internals: "emulated curator"→"the inbox read", "fan-out"→"discovered", removed store-placement reasoning and "polyfill/shim/mono-store" wording (FestipodDataContext, registration, storeRegistry, ngSession, AccountContext, isolation, sharedWallet, AccessGateScreen). Executable logic unchanged. - Removed dangling references to the dissolved `nextgraph-platform` concept and `brief_2026-06-15_shared-wallet-shim` from app code. - knowledge_nextgraph-stack: dropped "mécanique d'émulation" from the boundary note. - Settled and deleted all concept _debt.md (confirmatory; target leaves clean). (Test-infra under workshop/ + generated features.ts still carry some simulation vocabulary — parked as a separate below-SDK decision.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
/**
|
||||
* AccountContext — the *simulated* application-level login.
|
||||
* AccountContext — the application-level login.
|
||||
*
|
||||
* STOPGAP — part of the shared-wallet shim (see
|
||||
* .project/concepts/nextgraph-platform/brief_2026-06-15_shared-wallet-shim.md
|
||||
* and decision_2026-06-15_shared-wallet-login-flow.md).
|
||||
* STOPGAP (see decision_2026-06-15_shared-wallet-login-flow.md).
|
||||
*
|
||||
* The real NextGraph login (a redirect to the broker, opening the single
|
||||
* SHARED wallet) is perceived by the user as a *technical access barrier*,
|
||||
|
||||
@@ -52,7 +52,7 @@ interface FestipodDataContextValue {
|
||||
participations: FpParticipationData[];
|
||||
meetingPoints: FpMeetingPointData[];
|
||||
friendships: FpFriendshipData[];
|
||||
/** Host-facing notifications, surfaced from the inbox curator (T02.c). */
|
||||
/** Host-facing notifications, surfaced from the inbox read (T02.c). */
|
||||
notifications: FpNotificationData[];
|
||||
|
||||
getEvent(id: string): FpEventData | undefined;
|
||||
@@ -324,7 +324,7 @@ function useNgData(): FestipodDataContextValue {
|
||||
const usersShape = useShapeWithDefaults(FpUserProfileShapeType, protectedScope, emptyUsers, mapUser, true);
|
||||
const participationsShape = useShapeWithDefaults(FpParticipationShapeType, protectedScope, emptyParticipations, mapParticipation, true);
|
||||
|
||||
// Cross-account public discovery: read the fan-out documents as events.
|
||||
// Cross-account public discovery: read the discovered documents as events.
|
||||
const discoveryShape = useShapeWithDefaults(FpEventShapeType, discoveryScope, emptyEvents, mapEvent, true);
|
||||
|
||||
// Union the current-scope events with the cross-account discovered ones,
|
||||
@@ -345,7 +345,7 @@ function useNgData(): FestipodDataContextValue {
|
||||
const [meetingPoints, setMeetingPoints] = useState<FpMeetingPointData[]>([]);
|
||||
const [friendships, setFriendships] = useState<FpFriendshipData[]>([]);
|
||||
// Host-facing notifications, materialized from the current user's inboxes
|
||||
// (the emulated curator, T02.b/c). Data-level surfacing of "new participants".
|
||||
// (the inbox read, T02.b/c). Data-level surfacing of "new participants".
|
||||
const [notifications, setNotifications] = useState<FpNotificationData[]>([]);
|
||||
|
||||
const [selectedEventId, setSelectedEventId] = useState<string>('');
|
||||
@@ -396,7 +396,7 @@ function useNgData(): FestipodDataContextValue {
|
||||
const selectedUser = users.find(u => u.id === selectedUserId);
|
||||
|
||||
// --- Notification materialization (T02.c) ---------------------------------
|
||||
// Run the emulated inbox curator over the current user's hosted events and
|
||||
// Run the inbox read over the current user's hosted events and
|
||||
// surface "new participant" deposits as host-facing FpNotifications. Keyed on
|
||||
// the events the user hosts/selects; polls once per (events, selectedEvent).
|
||||
// Data-level surfacing — the notification module reads `notifications`.
|
||||
@@ -410,7 +410,7 @@ function useNgData(): FestipodDataContextValue {
|
||||
(async () => {
|
||||
try {
|
||||
// The SDK resolves the inbox anchor for the current session; read it ONCE
|
||||
// and let the curator filter deposits per hosted event.
|
||||
// and let the inbox filter deposits per hosted event.
|
||||
const targetInbox = await hostInboxNuri('');
|
||||
const all: FpNotificationData[] = [];
|
||||
for (const evId of hostedEventIds) {
|
||||
@@ -545,7 +545,7 @@ function useNgData(): FestipodDataContextValue {
|
||||
await insertNotification(protectedGraph, notif).catch(() => { /* data-level best-effort */ });
|
||||
// Surface immediately in reactive state (materialization also refreshes it).
|
||||
// Use the stable per-deposit uid for the id (F5 dedup) so it matches the
|
||||
// curator-materialized id and same-ms/anon deposits never collide.
|
||||
// notification id from the inbox and same-ms/anon deposits never collide.
|
||||
setNotifications(prev => [...prev, { ...notif, id: `notif-${depositUid}` }]);
|
||||
} catch (err) {
|
||||
console.error('[FestipodData] joinEvent inbox/notify failed:', err);
|
||||
|
||||
@@ -67,11 +67,8 @@ function mintDepositUid(): string {
|
||||
* Preference order: the explicit MeetingPoint `inbox` NURI (SHEX field, T02.a)
|
||||
* when known → else the SDK-resolved inbox anchor for the current session
|
||||
* (`resolveInboxAnchor()`). The app asks the SDK for the anchor by intent and
|
||||
* holds NO store id: the SDK owns where deposits physically land (today: the
|
||||
* shared wallet's private store — a real repo NURI, required because the broker
|
||||
* rejects a `urn:` anchor; deposits are discriminated by their `eventId`
|
||||
* payload, the curator filters per event). At migration the SDK returns the
|
||||
* host's native inbox NURI and this call is unchanged.
|
||||
* holds NO store id: the SDK owns where deposits land. Deposits are
|
||||
* discriminated by their `eventId` payload, and the inbox filters per event.
|
||||
*/
|
||||
export async function hostInboxNuri(eventId: string, explicitInbox?: string): Promise<string> {
|
||||
void eventId; // reserved: per-event inbox docs at migration
|
||||
@@ -124,7 +121,7 @@ export async function depositRegistration(
|
||||
|
||||
/**
|
||||
* Materialize a host inbox's deposits into host-facing notifications (data-level
|
||||
* surfacing). The emulated curator (`inbox.read`) returns the raw deposits; we
|
||||
* surfacing). The inbox read (`inbox.read`) returns the raw deposits; we
|
||||
* map each registration deposit to an `FpNotificationData` for `recipientId`.
|
||||
*/
|
||||
export async function readRegistrationNotifications(
|
||||
@@ -136,8 +133,8 @@ export async function readRegistrationNotifications(
|
||||
for (const d of deposits) {
|
||||
const p = d.payload as Partial<RegistrationPayload> | null;
|
||||
if (!p || p.kind !== NOTIF_TYPE_NEW_PARTICIPANT || !p.eventId) continue;
|
||||
// The polyfill inbox is shared (private store): keep only deposits for the
|
||||
// event whose host is reading. `recipientEventId` doubles as the recipient.
|
||||
// Keep only deposits for the event whose host is reading.
|
||||
// `recipientEventId` doubles as the recipient.
|
||||
if (recipientEventId && p.eventId !== recipientEventId) continue;
|
||||
const built = buildNotification(recipientEventId, p.eventId, d.from ?? null, d.ts);
|
||||
// F5 dedup: prefer the stable per-deposit uid carried in the payload so
|
||||
@@ -319,7 +316,7 @@ export async function insertNotification(
|
||||
const subject = `urn:festipod:notif:${Date.now()}:${Math.random().toString(36).slice(2)}`;
|
||||
// recipient/ref are bare domain ids ("user-1", "event-1"), not absolute IRIs;
|
||||
// store them as string literals to keep the INSERT valid (the raw shape read
|
||||
// is not the primary surfacing path — the inbox curator is). Every literal is
|
||||
// is not the primary surfacing path — the inbox read is). Every literal is
|
||||
// escaped via the lib's escapeLiteral (guards \ " \n \r \t — SPARQL injection).
|
||||
const refTriple = notif.ref ? `\n <${P.ref}> "${escapeLiteral(notif.ref)}" ;` : '';
|
||||
const payloadTriple = notif.payload
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
/**
|
||||
* isolation — app-level enforcement of the authorization matrix.
|
||||
* isolation — app-side visibility filter for the authorization matrix.
|
||||
*
|
||||
* STOPGAP (see brief_2026-06-15_shared-wallet-shim): one shared wallet means
|
||||
* everything is physically readable. To make staging *behave* like the target
|
||||
* infra, the app HONORS the matrix by filtering reads by owner + connections:
|
||||
* STOPGAP: the app HONORS the matrix by filtering reads by owner + connections:
|
||||
*
|
||||
* - public (events, meeting points) → visible to everyone
|
||||
* - protected (participations, connections) → owner + connections
|
||||
* - private (settings) → owner only
|
||||
*
|
||||
* This is NOT crypto-enforced — it's a deliberate, removable scaffold (the real
|
||||
* crypto isolation arrives with per-user wallets). Applied in CONNECTED mode
|
||||
* only; demo/@ui mode keeps full seed data.
|
||||
* This is a deliberate, removable scaffold. Applied in CONNECTED mode only;
|
||||
* demo/@ui mode keeps full seed data.
|
||||
*
|
||||
* Pure functions — no NextGraph, no React. Trivially testable.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Injection point — the ONLY app module that imports the real @ng-org SDK, to
|
||||
// inject it into the ng-eventually polyfill. Every other Festipod module gets
|
||||
// inject it into @ng-eventually/client. Every other Festipod module gets
|
||||
// its NextGraph surface from @ng-eventually/client. Removed at migration.
|
||||
import { ng as realNg, init as realInit } from "@ng-org/web";
|
||||
import type { NG } from "@ng-eventually/client";
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
/**
|
||||
* storeRegistry (Festipod glue) — the GENERIC mechanism now lives in the lib
|
||||
* (`@ng-eventually/client` `storeRegistry`, ported in T01.b). This file keeps
|
||||
* ONLY the Festipod domain mapping (entity kind → native scope) and injects the
|
||||
* consumer wiring the lib needs (session + username normalization) via
|
||||
* `configureStoreRegistry(...)`.
|
||||
*
|
||||
* The lib knows only the three native scopes (`public|protected|private`) and
|
||||
* performs all NextGraph I/O through the real injected `ng` (never the public
|
||||
* proxy → no DataCloneError). Everything the app previously implemented here
|
||||
* (shim model, doc_create, SPARQL r/w, index/fan-out) is now the lib's job; the
|
||||
* app re-exports the lib surface so existing callers stay unchanged. See
|
||||
* decision_2026-06-17_eventually-library and brief_2026-06-15_shared-wallet-shim.
|
||||
* storeRegistry (Festipod glue) — the lib owns placement; the app maps
|
||||
* entity → scope. This file keeps ONLY the Festipod domain mapping (entity kind
|
||||
* → scope) and injects the consumer wiring the lib needs (session + username
|
||||
* normalization) via `configureStoreRegistry(...)`. The app re-exports the lib
|
||||
* surface so existing callers stay unchanged.
|
||||
*/
|
||||
|
||||
import {
|
||||
@@ -41,17 +34,16 @@ export function entityScope(kind: EntityKind): Scope {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Consumer wiring injected into the lib's storeRegistry (polyfill-era) ---
|
||||
// The lib is Festipod-agnostic: it reaches the shared-wallet session and the
|
||||
// username normalization through these injected deps. Idempotent module-load
|
||||
// side effect (the app imports storeRegistry before any registry call).
|
||||
// --- Consumer wiring injected into the lib's storeRegistry ---
|
||||
// The lib is Festipod-agnostic: it reaches the session and the username
|
||||
// normalization through these injected deps. Idempotent module-load side effect
|
||||
// (the app imports storeRegistry before any registry call).
|
||||
configureStoreRegistry({
|
||||
getSession: async () => {
|
||||
const session = await sessionPromise;
|
||||
// Sanctioned injection point: the session (incl. the three native store ids)
|
||||
// is handed to the lib HERE and nowhere else. The lib owns physical placement
|
||||
// and resolves scope → store internally; the rest of the app speaks only in
|
||||
// logical scopes and never touches a store id / builds a `did:ng:${…}` NURI.
|
||||
// Sanctioned injection point: the session is handed to the lib HERE and
|
||||
// nowhere else. The lib owns placement and resolves scope internally; the
|
||||
// rest of the app speaks only in logical scopes.
|
||||
return {
|
||||
sessionId: session.session_id,
|
||||
privateStoreId: session.private_store_id,
|
||||
@@ -80,23 +72,15 @@ export const {
|
||||
} = libStoreRegistry;
|
||||
|
||||
/**
|
||||
* Create a per-entity document AND declare its ReadCap/WriteCap policy — the
|
||||
* app-side ACTIVATION of the emulated cap registry (dormant until an app
|
||||
* declares a policy). The lib's `createEntityDoc` stays domain-agnostic; the
|
||||
* DOMAIN mapping (scope → who may read) is Festipod's, so it lives here.
|
||||
*
|
||||
* In the target this is a native cap operation attached at store/repo creation;
|
||||
* here it is `getCaps().open(doc, scope, owner)`:
|
||||
* Create a per-entity document AND declare its ReadCap/WriteCap policy. The
|
||||
* lib's `createEntityDoc` stays domain-agnostic; the DOMAIN mapping (scope → who
|
||||
* may read) is Festipod's, so it lives here via `getCaps().open(doc, scope, owner)`:
|
||||
* - public → world-readable (`makePublic`) — events, meeting points
|
||||
* - protected → owner reads now; connections granted later (a separate grant)
|
||||
* - private → owner only
|
||||
* The owner always holds the WRITE cap (so only the owner may `sparql_update`
|
||||
* the doc once the guard is active). `owner` = the account username (the same
|
||||
* principal key the shim uses and that the app sets via `setCurrentUser`).
|
||||
*
|
||||
* NOTE ON BASELINE: `createEntityDoc` is only reached in MULTISTORE mode; the
|
||||
* default mono-store path never calls it and never sets a current user, so both
|
||||
* the ReadCap filter and the write guard stay inert (passthrough) by default.
|
||||
* The owner always holds the WRITE cap (so only the owner may update the doc once
|
||||
* the guard is active). `owner` = the account username (the principal the app
|
||||
* sets via `setCurrentUser`).
|
||||
*/
|
||||
export async function createEntityDoc(username: string, scope: Scope): Promise<string> {
|
||||
const entityNuri = await libStoreRegistry.createEntityDoc(username, scope);
|
||||
|
||||
Reference in New Issue
Block a user