()
recipients.add(node.createdBy)
+ // Grant-index recipients apply regardless of whether the schema declares
+ // roles: a node shared purely via a hub/share grant must still reach its
+ // grantees, including legacy (authorization-less) schemas. Folding this in
+ // for every path fixes the owner-only lockout (exploration 0192, Landmine #3).
+ const addGrantRecipients = () => {
+ const grants = dependencies.grantIndex?.findGrantsForResource(node.id) ?? []
+ for (const grant of grants) {
+ const actions = parseGrantActions(grant.properties.actions)
+ if (actions.includes('read') || actions.includes('write')) {
+ const grantee = grant.properties.grantee
+ if (typeof grantee === 'string' && grantee.startsWith('did:key:')) {
+ recipients.add(grantee as DID)
+ }
+ }
+ }
+ }
+
if (!schema.authorization) {
+ addGrantRecipients()
return [...recipients]
}
@@ -46,6 +64,7 @@ export async function computeRecipients(
const readExpr = auth.actions.read
if (!readExpr) {
+ addGrantRecipients()
return [...recipients]
}
@@ -64,16 +83,7 @@ export async function computeRecipients(
}
}
- const grants = dependencies.grantIndex?.findGrantsForResource(node.id) ?? []
- for (const grant of grants) {
- const actions = parseGrantActions(grant.properties.actions)
- if (actions.includes('read') || actions.includes('write')) {
- const grantee = grant.properties.grantee
- if (typeof grantee === 'string' && grantee.startsWith('did:key:')) {
- recipients.add(grantee as DID)
- }
- }
- }
+ addGrantRecipients()
return [...recipients]
}
diff --git a/packages/data/src/index.ts b/packages/data/src/index.ts
index d50b62eee..a85ce4c08 100644
--- a/packages/data/src/index.ts
+++ b/packages/data/src/index.ts
@@ -734,7 +734,10 @@ export {
describeRoleResolver,
type PermissionMatrix,
type ActionPermission,
- type RoleSummary
+ type RoleSummary,
+ schemaToHubPolicy,
+ hubActionsForSpaceRole,
+ type HubPolicy
} from './auth'
// Blob service
diff --git a/packages/data/src/schema/define.ts b/packages/data/src/schema/define.ts
index 49c7b6e7a..c6a222f12 100644
--- a/packages/data/src/schema/define.ts
+++ b/packages/data/src/schema/define.ts
@@ -16,8 +16,9 @@ import type {
InferNode
} from './types'
import type { AuthorizationDefinition } from '@xnetjs/core'
-import { validateAuthorization, serializeAuthorization } from '../auth'
+import { validateAuthorization, serializeAuthorization, warnLegacySchema } from '../auth'
import { createNodeId } from './node'
+import { isAuthExemptSchema } from './schemas/auth-exempt'
/**
* Default schema version when not specified.
@@ -152,6 +153,16 @@ export function defineSchema>(
authorization: options.authorization ? serializeAuthorization(options.authorization) : undefined
}
+ // ─── Dev-time guard rail: every schema should declare an authorization block
+ // (or be on the intentional auth-exempt allowlist). Without one the engine
+ // treats nodes as owner-only "legacy" mode (exploration 0192). The coverage
+ // test enforces this in CI; this warning surfaces it while authoring in the
+ // dev server. Gated to `development` (not merely non-production) so the many
+ // ad-hoc minimal schemas defined inside test suites don't spam warnings.
+ if (process.env.NODE_ENV === 'development' && !isAuthExemptSchema(schemaId)) {
+ warnLegacySchema(schema)
+ }
+
// Validation function
function validate(node: unknown): ValidationResult {
const errors: ValidationError[] = []
diff --git a/packages/data/src/schema/schemas/auth-exempt.ts b/packages/data/src/schema/schemas/auth-exempt.ts
new file mode 100644
index 000000000..7c04b96f7
--- /dev/null
+++ b/packages/data/src/schema/schemas/auth-exempt.ts
@@ -0,0 +1,49 @@
+/**
+ * Schemas that intentionally carry no `authorization` block (exploration 0192).
+ *
+ * Every other registered schema must declare authorization (enforced by
+ * `authorization-coverage.test.ts`). The schemas listed here are edge/system/
+ * identity nodes whose access semantics deliberately differ from the Space
+ * cascade and are governed elsewhere (the hub grant model, signed-content
+ * federation, or per-user privacy):
+ *
+ * - `SpaceMembership` — an edge node; who may read/write a membership is the
+ * membership *admin* question, secured by the hub against the parent Space,
+ * not a cascade of the membership row itself.
+ * - `Grant` — the capability record that the grant model is *made of*; gating
+ * it with the grant model would be circular. Hub-managed.
+ * - `Profile` — public identity. Profiles must be readable by collaborators to
+ * render names/handles on shared content, so they are hub-published rather
+ * than Space-gated.
+ * - `SchemaDefinition` / `SchemaCompatibility` / `SyncPolicy` — system /
+ * federation nodes; signed and content-addressed, governed by the schema
+ * authority resolution path, not per-node roles.
+ * - `PresenceSummary` — ephemeral presence aggregate; visibility is handled by
+ * the presence pipeline.
+ *
+ * This is the single source of truth: `defineSchema` reads it to suppress the
+ * dev-time legacy warning, and the coverage test reads it to allow these IRIs.
+ * Both the versioned (canonical) and unversioned (legacy alias) IRIs are listed
+ * so the check matches however a schema id is resolved.
+ */
+export const AUTH_EXEMPT_SCHEMA_IRIS: ReadonlySet = new Set([
+ 'xnet://xnet.fyi/SpaceMembership@1.0.0',
+ 'xnet://xnet.fyi/SpaceMembership',
+ 'xnet://xnet.fyi/Grant@1.0.0',
+ 'xnet://xnet.fyi/Grant',
+ 'xnet://xnet.fyi/Profile@1.0.0',
+ 'xnet://xnet.fyi/Profile',
+ 'xnet://xnet.fyi/SchemaDefinition@1.0.0',
+ 'xnet://xnet.fyi/SchemaDefinition',
+ 'xnet://xnet.fyi/SchemaCompatibility@1.0.0',
+ 'xnet://xnet.fyi/SchemaCompatibility',
+ 'xnet://xnet.fyi/SyncPolicy@1.0.0',
+ 'xnet://xnet.fyi/SyncPolicy',
+ 'xnet://xnet.fyi/PresenceSummary@1.0.0',
+ 'xnet://xnet.fyi/PresenceSummary'
+])
+
+/** Whether a schema id is on the intentional authorization-exempt allowlist. */
+export function isAuthExemptSchema(schemaId: string): boolean {
+ return AUTH_EXEMPT_SCHEMA_IRIS.has(schemaId)
+}
diff --git a/packages/data/src/schema/schemas/authorization-coverage.test.ts b/packages/data/src/schema/schemas/authorization-coverage.test.ts
new file mode 100644
index 000000000..d50c0c860
--- /dev/null
+++ b/packages/data/src/schema/schemas/authorization-coverage.test.ts
@@ -0,0 +1,65 @@
+/**
+ * Authorization coverage guard rail (exploration 0192).
+ *
+ * Every built-in schema must either declare an `authorization` block or appear
+ * on the intentional `AUTH_EXEMPT_SCHEMA_IRIS` allowlist. A schema with no
+ * authorization is "legacy" (owner-only) in the policy engine's eyes — so a new
+ * content type shipping without a policy silently becomes un-shareable the day
+ * the evaluator is wired in. This test freezes coverage and forces a per-schema
+ * decision for every future schema.
+ */
+import { describe, expect, it } from 'vitest'
+import { getAuthMode } from '../../auth'
+import { AUTH_EXEMPT_SCHEMA_IRIS, isAuthExemptSchema } from './auth-exempt'
+import { builtInSchemas } from './index'
+
+describe('authorization coverage', () => {
+ it('every registered schema declares authorization or is explicitly exempt', async () => {
+ const offenders: string[] = []
+ const seen = new Set()
+
+ for (const load of Object.values(builtInSchemas)) {
+ const defined = await load()
+ const schema = defined.schema
+ const id = schema['@id']
+ if (seen.has(id)) continue
+ seen.add(id)
+
+ if (getAuthMode(schema) === 'legacy' && !isAuthExemptSchema(id)) {
+ offenders.push(`${schema.name} (${id})`)
+ }
+ }
+
+ expect(
+ offenders,
+ `Schemas missing an authorization block (add spaceCascadeAuthorization() ` +
+ `or add the IRI to AUTH_EXEMPT_SCHEMA_IRIS with a justification): ` +
+ offenders.join(', ')
+ ).toEqual([])
+ })
+
+ it('every auth-exempt schema is actually registered and actually legacy', async () => {
+ // Guards against the allowlist rotting: an exempt entry that no longer
+ // exists, or one that has since gained an authorization block (and should
+ // therefore be removed from the exemption).
+ const byId = new Map<
+ string,
+ Awaited>['schema']
+ >()
+ for (const load of Object.values(builtInSchemas)) {
+ const defined = await load()
+ byId.set(defined.schema['@id'], defined.schema)
+ }
+
+ const stale: string[] = []
+ for (const iri of AUTH_EXEMPT_SCHEMA_IRIS) {
+ const schema = byId.get(iri)
+ if (!schema) continue // unversioned aliases resolve to the same @id; skip
+ if (getAuthMode(schema) !== 'legacy') {
+ stale.push(`${schema.name} (${iri}) now declares authorization — remove the exemption`)
+ }
+ }
+
+ expect(stale, stale.join(', ')).toEqual([])
+ })
+})
diff --git a/packages/data/src/schema/schemas/canvas.ts b/packages/data/src/schema/schemas/canvas.ts
index 706c8008d..e3ed14a7d 100644
--- a/packages/data/src/schema/schemas/canvas.ts
+++ b/packages/data/src/schema/schemas/canvas.ts
@@ -10,6 +10,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { text, relation, select } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const CanvasSchema = defineSchema({
name: 'Canvas',
@@ -44,7 +45,9 @@ export const CanvasSchema = defineSchema({
default: 'inherit'
})
},
- document: 'yjs' // Collaborative Y.Doc for canvas data (nodes, edges)
+ document: 'yjs', // Collaborative Y.Doc for canvas data (nodes, edges)
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization()
})
/**
diff --git a/packages/data/src/schema/schemas/channel.ts b/packages/data/src/schema/schemas/channel.ts
index 9a2d013dc..cddc4bd10 100644
--- a/packages/data/src/schema/schemas/channel.ts
+++ b/packages/data/src/schema/schemas/channel.ts
@@ -16,6 +16,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { checkbox, created, createdBy, person, relation, select, text } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const CHANNEL_KINDS = ['channel', 'dm', 'voice'] as const
export type ChannelKind = (typeof CHANNEL_KINDS)[number]
@@ -76,7 +77,9 @@ export const ChannelSchema = defineSchema({
createdAt: created(),
createdBy: createdBy()
},
- document: undefined
+ document: undefined,
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization()
})
export type Channel = InferNode<(typeof ChannelSchema)['_properties']>
diff --git a/packages/data/src/schema/schemas/chat-message.ts b/packages/data/src/schema/schemas/chat-message.ts
index b3bebb6ed..13f491c07 100644
--- a/packages/data/src/schema/schemas/chat-message.ts
+++ b/packages/data/src/schema/schemas/chat-message.ts
@@ -16,6 +16,7 @@ import type { InferNode } from '../types'
import type { MessageMentions } from './mentions'
import { defineSchema } from '../define'
import { checkbox, created, createdBy, date, file, json, relation, text } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const ChatMessageSchema = defineSchema({
name: 'ChatMessage',
@@ -54,7 +55,9 @@ export const ChatMessageSchema = defineSchema({
createdAt: created(),
createdBy: createdBy()
},
- document: undefined
+ document: undefined,
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization('channel')
})
export type ChatMessage = InferNode<(typeof ChatMessageSchema)['_properties']>
diff --git a/packages/data/src/schema/schemas/comment.ts b/packages/data/src/schema/schemas/comment.ts
index d536dfa3d..cf45eea8d 100644
--- a/packages/data/src/schema/schemas/comment.ts
+++ b/packages/data/src/schema/schemas/comment.ts
@@ -22,6 +22,7 @@ import {
createdBy,
json
} from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const CommentSchema = defineSchema({
name: 'Comment',
@@ -112,7 +113,9 @@ export const CommentSchema = defineSchema({
},
// Comments are plain text + markdown, no collaborative Y.Doc needed
- document: undefined
+ document: undefined,
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization('target')
})
/**
diff --git a/packages/data/src/schema/schemas/dashboard.ts b/packages/data/src/schema/schemas/dashboard.ts
index 098775270..ff72d0217 100644
--- a/packages/data/src/schema/schemas/dashboard.ts
+++ b/packages/data/src/schema/schemas/dashboard.ts
@@ -17,6 +17,7 @@ import type { SavedViewDescriptor } from '../../store/query-ast'
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { json, relation, select, text } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
/** Refresh policy for a widget's query subscription. */
export type DashboardWidgetRefresh = 'live' | 'on-open' | { intervalMs: number }
@@ -120,7 +121,9 @@ export const DashboardSchema = defineSchema({
] as const,
default: 'inherit'
})
- }
+ },
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization()
})
/**
diff --git a/packages/data/src/schema/schemas/database-field.ts b/packages/data/src/schema/schemas/database-field.ts
index e35a7da73..db35abb69 100644
--- a/packages/data/src/schema/schemas/database-field.ts
+++ b/packages/data/src/schema/schemas/database-field.ts
@@ -19,6 +19,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { text, relation, number, checkbox, json } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const DatabaseFieldSchema = defineSchema({
name: 'DatabaseField',
@@ -55,7 +56,9 @@ export const DatabaseFieldSchema = defineSchema({
/** Hidden by default (views can override) */
hidden: checkbox({})
- }
+ },
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization('database')
})
/**
diff --git a/packages/data/src/schema/schemas/database-row.ts b/packages/data/src/schema/schemas/database-row.ts
index 5739f1e58..4e057ced6 100644
--- a/packages/data/src/schema/schemas/database-row.ts
+++ b/packages/data/src/schema/schemas/database-row.ts
@@ -15,6 +15,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { text, relation } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const DatabaseRowSchema = defineSchema({
name: 'DatabaseRow',
@@ -46,7 +47,9 @@ export const DatabaseRowSchema = defineSchema({
* Only created when the row has rich text columns.
* Each rich text column gets its own Y.XmlFragment in the doc.
*/
- document: 'yjs'
+ document: 'yjs',
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization('database')
})
/**
diff --git a/packages/data/src/schema/schemas/database-select-option.ts b/packages/data/src/schema/schemas/database-select-option.ts
index 8e0dd7e32..a614b5f15 100644
--- a/packages/data/src/schema/schemas/database-select-option.ts
+++ b/packages/data/src/schema/schemas/database-select-option.ts
@@ -13,6 +13,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { text, relation } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const DatabaseSelectOptionSchema = defineSchema({
name: 'DatabaseSelectOption',
@@ -41,7 +42,9 @@ export const DatabaseSelectOptionSchema = defineSchema({
/** Fractional index for option ordering in pickers */
sortKey: text({ required: true })
- }
+ },
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization('database')
})
/**
diff --git a/packages/data/src/schema/schemas/database-view.ts b/packages/data/src/schema/schemas/database-view.ts
index 3e8397da7..a1cf9ae9a 100644
--- a/packages/data/src/schema/schemas/database-view.ts
+++ b/packages/data/src/schema/schemas/database-view.ts
@@ -17,6 +17,7 @@ import type { FilterGroup, SortConfig } from '../../database/view-types'
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { text, relation, select, json } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const DatabaseViewSchema = defineSchema({
name: 'DatabaseView',
@@ -84,7 +85,9 @@ export const DatabaseViewSchema = defineSchema({
/** End date field ID */
endDateField: text({ maxLength: 100 })
- }
+ },
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization('database')
})
/**
diff --git a/packages/data/src/schema/schemas/database.ts b/packages/data/src/schema/schemas/database.ts
index 4916bc012..df841491e 100644
--- a/packages/data/src/schema/schemas/database.ts
+++ b/packages/data/src/schema/schemas/database.ts
@@ -18,6 +18,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { text, file, select, number, relation } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const DatabaseSchema = defineSchema({
name: 'Database',
@@ -84,7 +85,9 @@ export const DatabaseSchema = defineSchema({
})
},
// Y.Doc used ONLY as the awareness/presence channel — no persistent state
- document: 'yjs'
+ document: 'yjs',
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization()
})
/**
diff --git a/packages/data/src/schema/schemas/experiment.ts b/packages/data/src/schema/schemas/experiment.ts
index 873b7a6d5..2d824cb4d 100644
--- a/packages/data/src/schema/schemas/experiment.ts
+++ b/packages/data/src/schema/schemas/experiment.ts
@@ -12,6 +12,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { text, select, json, date, relation } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const ExperimentSchema = defineSchema({
name: 'Experiment',
@@ -108,7 +109,9 @@ export const ExperimentSchema = defineSchema({
default: 'private'
})
},
- document: 'yjs' // Collaborative protocol / journal / observations narrative
+ document: 'yjs', // Collaborative protocol / journal / observations narrative
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization()
})
/** An Experiment node type (inferred from schema). */
diff --git a/packages/data/src/schema/schemas/external-reference.ts b/packages/data/src/schema/schemas/external-reference.ts
index 982b3a9d4..229baff4c 100644
--- a/packages/data/src/schema/schemas/external-reference.ts
+++ b/packages/data/src/schema/schemas/external-reference.ts
@@ -8,6 +8,7 @@
*/
import type { InferNode } from '../types'
+import { presets } from '../../auth'
import { defineSchema } from '../define'
import { select, text, url } from '../properties'
@@ -69,7 +70,9 @@ export const ExternalReferenceSchema = defineSchema({
/** Provider-specific metadata stored as JSON */
metadata: text({ maxLength: 10000 })
},
- document: undefined
+ document: undefined,
+ // Standalone/personal content: owner-only by default (exploration 0192).
+ authorization: presets.private()
})
/**
diff --git a/packages/data/src/schema/schemas/folder.ts b/packages/data/src/schema/schemas/folder.ts
index 84f00c9f8..36a1d1005 100644
--- a/packages/data/src/schema/schemas/folder.ts
+++ b/packages/data/src/schema/schemas/folder.ts
@@ -12,6 +12,7 @@
*/
import type { InferNode } from '../types'
+import { presets } from '../../auth'
import { compareSortKeys } from '../../database/fractional-index'
import { defineSchema } from '../define'
import { created, createdBy, relation, text } from '../properties'
@@ -37,7 +38,9 @@ export const FolderSchema = defineSchema({
createdAt: created(),
createdBy: createdBy()
},
- document: undefined
+ document: undefined,
+ // Standalone/personal content: owner-only by default (exploration 0192).
+ authorization: presets.private()
})
export type Folder = InferNode<(typeof FolderSchema)['_properties']>
diff --git a/packages/data/src/schema/schemas/inbox-state.ts b/packages/data/src/schema/schemas/inbox-state.ts
index 951163d74..4eaee17ad 100644
--- a/packages/data/src/schema/schemas/inbox-state.ts
+++ b/packages/data/src/schema/schemas/inbox-state.ts
@@ -15,6 +15,7 @@
*/
import type { InferNode } from '../types'
+import { presets } from '../../auth'
import { defineSchema } from '../define'
import { created, createdBy, json, person } from '../properties'
@@ -71,7 +72,9 @@ export const InboxStateSchema = defineSchema({
createdAt: created(),
createdBy: createdBy()
},
- document: undefined
+ document: undefined,
+ // Standalone/personal content: owner-only by default (exploration 0192).
+ authorization: presets.private()
})
export type InboxState = InferNode<(typeof InboxStateSchema)['_properties']>
diff --git a/packages/data/src/schema/schemas/map.ts b/packages/data/src/schema/schemas/map.ts
index a132808b3..a324f2cad 100644
--- a/packages/data/src/schema/schemas/map.ts
+++ b/packages/data/src/schema/schemas/map.ts
@@ -17,6 +17,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { json, relation, select, text } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
// ─── Minimal structural GeoJSON (dependency-free) ────────────────────────────
// Just enough of the GeoJSON shape to persist imported features and feed them
@@ -155,7 +156,9 @@ export const MapSchema = defineSchema({
] as const,
default: 'inherit'
})
- }
+ },
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization()
})
/**
diff --git a/packages/data/src/schema/schemas/media-asset.ts b/packages/data/src/schema/schemas/media-asset.ts
index 7285927d6..3f8c1e654 100644
--- a/packages/data/src/schema/schemas/media-asset.ts
+++ b/packages/data/src/schema/schemas/media-asset.ts
@@ -3,6 +3,7 @@
*/
import type { InferNode } from '../types'
+import { presets } from '../../auth'
import { defineSchema } from '../define'
import { file, number, select, text } from '../properties'
@@ -37,7 +38,9 @@ export const MediaAssetSchema = defineSchema({
/** Natural height when known */
height: number({ integer: true, min: 0 })
},
- document: undefined
+ document: undefined,
+ // Standalone/personal content: owner-only by default (exploration 0192).
+ authorization: presets.private()
})
export type MediaAsset = InferNode<(typeof MediaAssetSchema)['_properties']>
diff --git a/packages/data/src/schema/schemas/metric.ts b/packages/data/src/schema/schemas/metric.ts
index af78c4e2a..9c7d709a9 100644
--- a/packages/data/src/schema/schemas/metric.ts
+++ b/packages/data/src/schema/schemas/metric.ts
@@ -12,6 +12,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { text, number, select, json, relation } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const MetricSchema = defineSchema({
name: 'Metric',
@@ -118,7 +119,9 @@ export const MetricSchema = defineSchema({
] as const,
default: 'private'
})
- }
+ },
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization()
})
/** A Metric node type (inferred from schema). */
diff --git a/packages/data/src/schema/schemas/observation.ts b/packages/data/src/schema/schemas/observation.ts
index 498ef8708..46a65e7a5 100644
--- a/packages/data/src/schema/schemas/observation.ts
+++ b/packages/data/src/schema/schemas/observation.ts
@@ -13,6 +13,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { text, number, select, json, date, relation } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const ObservationSchema = defineSchema({
name: 'Observation',
@@ -74,7 +75,9 @@ export const ObservationSchema = defineSchema({
] as const,
default: 'private'
})
- }
+ },
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization()
})
/** An Observation node type (inferred from schema). */
diff --git a/packages/data/src/schema/schemas/page.ts b/packages/data/src/schema/schemas/page.ts
index dfdee06e7..df1d3463e 100644
--- a/packages/data/src/schema/schemas/page.ts
+++ b/packages/data/src/schema/schemas/page.ts
@@ -10,6 +10,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { text, file, relation, select } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const PageSchema = defineSchema({
name: 'Page',
@@ -47,7 +48,9 @@ export const PageSchema = defineSchema({
default: 'inherit'
})
},
- document: 'yjs' // Collaborative Y.Doc for rich text
+ document: 'yjs', // Collaborative Y.Doc for rich text
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization()
})
/**
diff --git a/packages/data/src/schema/schemas/reaction.ts b/packages/data/src/schema/schemas/reaction.ts
index 0701e53d4..550330190 100644
--- a/packages/data/src/schema/schemas/reaction.ts
+++ b/packages/data/src/schema/schemas/reaction.ts
@@ -5,6 +5,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { created, createdBy, person, relation, select, text } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
const reactionTypes = [
{ id: 'like', name: 'Like', color: 'green' },
@@ -30,7 +31,9 @@ export const ReactionSchema = defineSchema({
createdAt: created(),
createdBy: createdBy()
},
- document: undefined
+ document: undefined,
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization('target')
})
export type Reaction = InferNode<(typeof ReactionSchema)['_properties']>
diff --git a/packages/data/src/schema/schemas/saved-view.ts b/packages/data/src/schema/schemas/saved-view.ts
index 206bb3732..4a372ed71 100644
--- a/packages/data/src/schema/schemas/saved-view.ts
+++ b/packages/data/src/schema/schemas/saved-view.ts
@@ -8,6 +8,7 @@
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { relation, select, text } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const SavedViewSchema = defineSchema({
name: 'SavedView',
@@ -34,7 +35,9 @@ export const SavedViewSchema = defineSchema({
/** Optional database that owns database-scoped views */
database: relation({ target: 'xnet://xnet.fyi/Database@2.0.0' as const })
- }
+ },
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization('database')
})
export type SavedView = InferNode<(typeof SavedViewSchema)['_properties']>
diff --git a/packages/data/src/schema/schemas/tag.ts b/packages/data/src/schema/schemas/tag.ts
index 11f081136..b0fa1a4db 100644
--- a/packages/data/src/schema/schemas/tag.ts
+++ b/packages/data/src/schema/schemas/tag.ts
@@ -14,6 +14,7 @@
*/
import type { InferNode } from '../types'
+import { presets } from '../../auth'
import { defineSchema } from '../define'
import { checkbox, created, createdBy, text } from '../properties'
@@ -40,7 +41,9 @@ export const TagSchema = defineSchema({
createdAt: created(),
createdBy: createdBy()
},
- document: undefined
+ document: undefined,
+ // Standalone/personal content: owner-only by default (exploration 0192).
+ authorization: presets.private()
})
export type Tag = InferNode<(typeof TagSchema)['_properties']>
diff --git a/packages/data/src/schema/schemas/task-view.ts b/packages/data/src/schema/schemas/task-view.ts
index 920248a47..43e7d87b7 100644
--- a/packages/data/src/schema/schemas/task-view.ts
+++ b/packages/data/src/schema/schemas/task-view.ts
@@ -11,6 +11,7 @@ import type { FilterGroup, SortConfig } from '../../database/view-types'
import type { InferNode } from '../types'
import { defineSchema } from '../define'
import { text, relation, select, json } from '../properties'
+import { spaceCascadeAuthorization } from './space-authorization'
export const TaskViewSchema = defineSchema({
name: 'TaskView',
@@ -48,7 +49,9 @@ export const TaskViewSchema = defineSchema({
/** Fractional index for view tab ordering */
sortKey: text({ required: true })
- }
+ },
+ // Inherits access from its home Space (exploration 0181/0192).
+ authorization: spaceCascadeAuthorization('project')
})
/**
diff --git a/packages/data/src/schema/schemas/user-widget.ts b/packages/data/src/schema/schemas/user-widget.ts
index a06535353..06d789987 100644
--- a/packages/data/src/schema/schemas/user-widget.ts
+++ b/packages/data/src/schema/schemas/user-widget.ts
@@ -8,6 +8,7 @@
*/
import type { InferNode } from '../types'
+import { presets } from '../../auth'
import { defineSchema } from '../define'
import { json, text } from '../properties'
@@ -44,7 +45,9 @@ export const UserWidgetSchema = defineSchema({
/** Default tile size in 12-column grid units — whole-value LWW */
defaultSize: json({})
- }
+ },
+ // Standalone/personal content: owner-only by default (exploration 0192).
+ authorization: presets.private()
})
export type UserWidget = InferNode<(typeof UserWidgetSchema)['_properties']>