@fayz-ai/core 0.8.1 → 0.8.3

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 (123) hide show
  1. package/dist/access/denial.d.ts +21 -0
  2. package/dist/access/denial.d.ts.map +1 -0
  3. package/dist/access/engine.d.ts +63 -0
  4. package/dist/access/engine.d.ts.map +1 -0
  5. package/dist/access/index.cjs +60 -0
  6. package/dist/access/index.cjs.map +1 -0
  7. package/dist/access/index.d.ts +6 -0
  8. package/dist/access/index.d.ts.map +1 -0
  9. package/dist/access/index.js +51 -0
  10. package/dist/access/index.js.map +1 -0
  11. package/dist/access/limits.d.ts +10 -0
  12. package/dist/access/limits.d.ts.map +1 -0
  13. package/dist/address/index.d.ts +52 -0
  14. package/dist/address/index.d.ts.map +1 -0
  15. package/dist/{chunk-QR7U2URY.cjs → chunk-CATASHPK.cjs} +345 -4
  16. package/dist/chunk-CATASHPK.cjs.map +1 -0
  17. package/dist/{chunk-WKZVSKEU.js → chunk-CU64QI4A.js} +2 -2
  18. package/dist/chunk-CU64QI4A.js.map +1 -0
  19. package/dist/{chunk-KNIVT6XZ.js → chunk-DZALZ6Q6.js} +345 -4
  20. package/dist/chunk-DZALZ6Q6.js.map +1 -0
  21. package/dist/{chunk-54SRL7AJ.cjs → chunk-KSI2L3E4.cjs} +145 -86
  22. package/dist/chunk-KSI2L3E4.cjs.map +1 -0
  23. package/dist/{chunk-ZBIWYDAR.js → chunk-TYPVSWLG.js} +144 -87
  24. package/dist/chunk-TYPVSWLG.js.map +1 -0
  25. package/dist/{chunk-2NTHNWEH.cjs → chunk-UKU4RIWM.cjs} +2 -2
  26. package/dist/chunk-UKU4RIWM.cjs.map +1 -0
  27. package/dist/{chunk-CHGX6UYR.js → chunk-YH25Y4FW.js} +6 -3
  28. package/dist/chunk-YH25Y4FW.js.map +1 -0
  29. package/dist/{chunk-5X2VX3RQ.cjs → chunk-ZOPJB5FN.cjs} +6 -3
  30. package/dist/chunk-ZOPJB5FN.cjs.map +1 -0
  31. package/dist/data/archetype.d.ts.map +1 -1
  32. package/dist/data/count.d.ts +34 -0
  33. package/dist/data/count.d.ts.map +1 -0
  34. package/dist/data/index.cjs +18 -10
  35. package/dist/data/index.d.ts +2 -0
  36. package/dist/data/index.d.ts.map +1 -1
  37. package/dist/data/index.js +1 -1
  38. package/dist/data/refresh.d.ts +36 -0
  39. package/dist/data/refresh.d.ts.map +1 -0
  40. package/dist/data/resolve.d.ts.map +1 -1
  41. package/dist/data/supabase.d.ts.map +1 -1
  42. package/dist/data/types.d.ts +6 -0
  43. package/dist/data/types.d.ts.map +1 -1
  44. package/dist/entity/index.cjs +6 -6
  45. package/dist/entity/index.js +1 -1
  46. package/dist/entity/registry.d.ts +9 -0
  47. package/dist/entity/registry.d.ts.map +1 -1
  48. package/dist/i18n/index.cjs +10 -10
  49. package/dist/i18n/index.js +1 -1
  50. package/dist/i18n/shell-translations.d.ts.map +1 -1
  51. package/dist/index.cjs +1517 -180
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.ts +15 -5
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +1412 -110
  56. package/dist/index.js.map +1 -1
  57. package/dist/manifest/index.d.ts +109 -3
  58. package/dist/manifest/index.d.ts.map +1 -1
  59. package/dist/manifest/serialize.d.ts +14 -0
  60. package/dist/manifest/serialize.d.ts.map +1 -0
  61. package/dist/plugin/index.cjs +7 -7
  62. package/dist/plugin/index.js +1 -1
  63. package/dist/plugin/runtime.d.ts.map +1 -1
  64. package/dist/search/engine.d.ts +12 -0
  65. package/dist/search/engine.d.ts.map +1 -0
  66. package/dist/search/index.d.ts +5 -0
  67. package/dist/search/index.d.ts.map +1 -0
  68. package/dist/search/text.d.ts +37 -0
  69. package/dist/search/text.d.ts.map +1 -0
  70. package/dist/search/types.d.ts +72 -0
  71. package/dist/search/types.d.ts.map +1 -0
  72. package/dist/types/billing.d.ts +20 -0
  73. package/dist/types/billing.d.ts.map +1 -1
  74. package/dist/types/crud.d.ts +47 -1
  75. package/dist/types/crud.d.ts.map +1 -1
  76. package/dist/types/entitlements.d.ts +45 -0
  77. package/dist/types/entitlements.d.ts.map +1 -0
  78. package/dist/types/index.d.ts +4 -3
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/dist/types/org.d.ts +28 -0
  81. package/dist/types/org.d.ts.map +1 -1
  82. package/dist/types/plugins.d.ts +88 -0
  83. package/dist/types/plugins.d.ts.map +1 -1
  84. package/package.json +14 -4
  85. package/src/access/denial.ts +33 -0
  86. package/src/access/engine.test.ts +48 -0
  87. package/src/access/engine.ts +121 -0
  88. package/src/access/index.ts +13 -0
  89. package/src/access/limits.ts +18 -0
  90. package/src/address/index.ts +188 -0
  91. package/src/data/archetype.ts +4 -2
  92. package/src/data/count.test.ts +152 -0
  93. package/src/data/count.ts +131 -0
  94. package/src/data/index.ts +2 -0
  95. package/src/data/refresh.ts +105 -0
  96. package/src/data/resolve.ts +7 -1
  97. package/src/data/supabase.ts +3 -1
  98. package/src/data/types.ts +6 -0
  99. package/src/entity/registry.ts +7 -0
  100. package/src/i18n/shell-translations.ts +345 -2
  101. package/src/index.ts +37 -4
  102. package/src/manifest/app-manifest.schema.json +618 -45
  103. package/src/manifest/index.ts +127 -3
  104. package/src/manifest/serialize.ts +52 -0
  105. package/src/plugin/runtime.ts +5 -1
  106. package/src/search/engine.ts +551 -0
  107. package/src/search/index.ts +25 -0
  108. package/src/search/text.ts +206 -0
  109. package/src/search/types.ts +75 -0
  110. package/src/types/billing.ts +22 -0
  111. package/src/types/crud.ts +48 -1
  112. package/src/types/entitlements.ts +45 -0
  113. package/src/types/index.ts +4 -3
  114. package/src/types/org.ts +29 -0
  115. package/src/types/plugins.ts +83 -0
  116. package/dist/chunk-2NTHNWEH.cjs.map +0 -1
  117. package/dist/chunk-54SRL7AJ.cjs.map +0 -1
  118. package/dist/chunk-5X2VX3RQ.cjs.map +0 -1
  119. package/dist/chunk-CHGX6UYR.js.map +0 -1
  120. package/dist/chunk-KNIVT6XZ.js.map +0 -1
  121. package/dist/chunk-QR7U2URY.cjs.map +0 -1
  122. package/dist/chunk-WKZVSKEU.js.map +0 -1
  123. package/dist/chunk-ZBIWYDAR.js.map +0 -1
@@ -1,7 +1,9 @@
1
1
  import type { EntityDef } from '../types/crud'
2
2
  import type { LocaleConfig } from '../types/i18n'
3
- import type { PermissionsConfig } from '../types/permissions'
3
+ import type { PermissionsConfig, FeatureDeclaration } from '../types/permissions'
4
4
  import type { BillingConfig } from '../types/billing'
5
+ import type { LimitDeclaration } from '../types/entitlements'
6
+ import type { AIToolParameters, AIToolExecution, AgentRpcDeclaration } from '../types/plugins'
5
7
  import type { BlockNode } from '../blocks'
6
8
 
7
9
  // ---------------------------------------------------------------------------
@@ -18,7 +20,7 @@ import type { BlockNode } from '../blocks'
18
20
  // right is why W1 is the "point of no return".
19
21
  // ---------------------------------------------------------------------------
20
22
 
21
- export const CURRENT_MANIFEST_VERSION = 2
23
+ export const CURRENT_MANIFEST_VERSION = 3
22
24
 
23
25
  export type BackendProvider = 'supabase' | 'fayz-api' | 'fayz-shop' | 'mock' | 'custom'
24
26
 
@@ -68,6 +70,97 @@ export interface SurfaceManifest {
68
70
  options?: Record<string, unknown>
69
71
  }
70
72
 
73
+ // ---------------------------------------------------------------------------
74
+ // Agent Contract (manifest v3) — everything a SERVER-SIDE agent runtime needs
75
+ // to operate the app without a browser: the full entity surface, the tool
76
+ // catalog with its authorization requirements, the pool RPCs writes go
77
+ // through, and the structured domain knowledge the app wants in the prompt.
78
+ // Derived from app code at publish (never hand-written) and synced to the
79
+ // Fayz platform, where it becomes the versioned AgentContract the broker
80
+ // validates every turn against.
81
+ // ---------------------------------------------------------------------------
82
+
83
+ /** JSON-safe projection of an EntityDef — schema only, no components/closures.
84
+ * `data` mirrors EntityDef.data: everything the server-plane read executor
85
+ * needs (tenant scoping, search/select column allowlists, fixed filters). */
86
+ export interface EntityContract {
87
+ key: string
88
+ label: string
89
+ labelPlural?: string
90
+ /** Plan-limit key rows of this entity consume (EntityDef.limitKey). */
91
+ limitKey?: string
92
+ /** Permission to READ via the generic data primitives (EntityDef.permission). */
93
+ permission?: { feature: string; action: string }
94
+ data: {
95
+ table: string
96
+ schema?: string
97
+ tenantScoped?: boolean
98
+ tenantIdColumn?: string
99
+ searchColumns?: string[]
100
+ selectColumns?: string
101
+ columnMap?: Record<string, string>
102
+ archetype?: string
103
+ archetypeKind?: string
104
+ filters?: Record<string, string>
105
+ }
106
+ fields: Array<{
107
+ key: string
108
+ label: string
109
+ type: string
110
+ required?: boolean
111
+ searchable?: boolean
112
+ /** FieldDef.hint — the field's helper text. Carried into the contract
113
+ * because it is what tells the agent what a non-obvious column expects:
114
+ * an id to look up, a default it should not restate, a format. */
115
+ hint?: string
116
+ /** Allowed values for select-like fields. */
117
+ options?: string[]
118
+ /** Table the relation field points at (FieldRelation.table). */
119
+ relationTable?: string
120
+ }>
121
+ }
122
+
123
+ /** JSON-safe projection of a PluginAITool + its owning plugin. */
124
+ export interface AIToolContract {
125
+ id: string
126
+ name: string
127
+ description: string
128
+ mode: 'read' | 'persist'
129
+ parameters?: AIToolParameters
130
+ permission?: { feature: string; action: string }
131
+ limitKey?: string
132
+ execution?: AIToolExecution
133
+ requiresConfirmation?: boolean
134
+ pluginId?: string
135
+ category?: string
136
+ }
137
+
138
+ /** A tenant-scoped pool RPC the server-plane executor may call. Canonical
139
+ * definition lives with the plugin types (plugins declare these). */
140
+ export type { AgentRpcDeclaration as RpcContract } from '../types/plugins'
141
+
142
+ /** Structured domain knowledge rendered into the agent's prompt as labelled
143
+ * sections — statuses with transitions, scheduling defaults, free-form rules. */
144
+ export interface AgentDomainKnowledge {
145
+ statuses?: Record<string, Array<{ id: string; label: string; availableWhen?: unknown }>>
146
+ scheduleDefaults?: Record<string, unknown>
147
+ businessRules?: Array<{ id: string; description: string }>
148
+ }
149
+
150
+ export interface AgentContract {
151
+ persona?: { name?: string; systemPrompt?: string; icon?: string }
152
+ /** When 'server', surfaces stop shipping data-tool executors: the broker
153
+ * executes reads/writes server-plane and the surface only handles
154
+ * client-plane tools. Flipped per app — no lockstep release. */
155
+ executionPlane?: 'client' | 'server'
156
+ entities?: EntityContract[]
157
+ tools?: AIToolContract[]
158
+ registries?: Array<{ pluginId: string; id: string; entityKey?: string; readOnly?: boolean }>
159
+ declaredFeatures?: FeatureDeclaration[]
160
+ rpcs?: AgentRpcDeclaration[]
161
+ domainKnowledge?: AgentDomainKnowledge
162
+ }
163
+
71
164
  export interface AppManifest {
72
165
  manifestVersion: number
73
166
  id: string
@@ -83,6 +176,13 @@ export interface AppManifest {
83
176
  entities?: EntityDef[]
84
177
  permissions?: PermissionsConfig
85
178
  billing?: BillingConfig
179
+ /** The RESOLVED limit declarations (4-layer merge: core < entity-derived <
180
+ * plugin < app), so a server runtime counts exactly what the client counts. */
181
+ limitDeclarations?: LimitDeclaration[]
182
+ /** v3: the agent contract — see the section comment above. */
183
+ agent?: AgentContract
184
+ /** sha256 of the canonical serialization (excluding this field). */
185
+ contractHash?: string
86
186
  }
87
187
 
88
188
  // ---------------------------------------------------------------------------
@@ -108,6 +208,9 @@ const allowedManifestKeys = new Set([
108
208
  'billing',
109
209
  'entities',
110
210
  'surfaces',
211
+ 'limitDeclarations',
212
+ 'agent',
213
+ 'contractHash',
111
214
  ])
112
215
  const allowedBackendKeys = new Set(['provider', 'projectRef', 'url', 'adapterId', 'options'])
113
216
  const allowedSurfaceKeys = new Set(['scaffold', 'options', 'plugins', 'pages'])
@@ -122,6 +225,12 @@ export function registerManifestMigration(fromVersion: number, fn: ManifestMigra
122
225
  migrations.set(fromVersion, fn)
123
226
  }
124
227
 
228
+ // v2 → v3 is purely additive (limitDeclarations/agent/contractHash are optional):
229
+ // a v2 manifest is a valid v3 manifest with those sections absent. Registered
230
+ // here so every consumer migrates for free; `fayz manifest emit` fills the new
231
+ // sections on the next regeneration.
232
+ registerManifestMigration(2, (m) => m)
233
+
125
234
  /** Bring any manifest up to CURRENT_MANIFEST_VERSION, or throw if it was built
126
235
  * for a newer SDK than this runtime understands. */
127
236
  export function migrateManifest(input: AnyManifest): AppManifest {
@@ -167,6 +276,21 @@ export function validateManifest(m: AppManifest): string[] {
167
276
  validateLooseObject(manifest.theme, 'manifest.theme', problems)
168
277
  validateLooseObject(manifest.permissions, 'manifest.permissions', problems)
169
278
  validateLooseObject(manifest.billing, 'manifest.billing', problems)
279
+ validateLooseObject(manifest.agent, 'manifest.agent', problems)
280
+ if (manifest.contractHash !== undefined && !isNonEmptyString(manifest.contractHash)) {
281
+ problems.push('manifest.contractHash must be a non-empty string')
282
+ }
283
+ if (manifest.limitDeclarations !== undefined) {
284
+ if (!Array.isArray(manifest.limitDeclarations)) {
285
+ problems.push('manifest.limitDeclarations must be an array')
286
+ } else {
287
+ manifest.limitDeclarations.forEach((decl, index) => {
288
+ if (!isRecord(decl) || !isNonEmptyString(decl.key) || !isNonEmptyString(decl.table)) {
289
+ problems.push(`manifest.limitDeclarations[${index}] must declare key and table`)
290
+ }
291
+ })
292
+ }
293
+ }
170
294
  if (manifest.entities !== undefined) {
171
295
  if (!Array.isArray(manifest.entities)) {
172
296
  problems.push('manifest.entities must be an array')
@@ -368,7 +492,7 @@ function addUnsupportedKeys(
368
492
  ): void {
369
493
  for (const key of Object.keys(value)) {
370
494
  if (!allowedKeys.has(key)) {
371
- problems.push(`${path}.${key} is not part of AppManifest v2`)
495
+ problems.push(`${path}.${key} is not part of AppManifest v${CURRENT_MANIFEST_VERSION}`)
372
496
  }
373
497
  }
374
498
  }
@@ -0,0 +1,52 @@
1
+ import type { AppManifest } from './index'
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Deterministic serialization — the SAME manifest object must always produce
5
+ // the SAME bytes, because the sha256 of those bytes (`contractHash`) is what
6
+ // makes `fayz manifest sync` idempotent and lets `fayz doctor` prove the
7
+ // committed app.manifest.json is not stale. Rules: object keys sorted, arrays
8
+ // kept in declared order (order is meaning: nav position, migration order),
9
+ // 2-space indent, trailing newline, `contractHash` itself excluded.
10
+ // ---------------------------------------------------------------------------
11
+
12
+ function sortValue(value: unknown): unknown {
13
+ if (Array.isArray(value)) return value.map(sortValue)
14
+ if (value !== null && typeof value === 'object') {
15
+ const record = value as Record<string, unknown>
16
+ const out: Record<string, unknown> = {}
17
+ for (const key of Object.keys(record).sort()) {
18
+ const v = record[key]
19
+ if (v !== undefined) out[key] = sortValue(v)
20
+ }
21
+ return out
22
+ }
23
+ return value
24
+ }
25
+
26
+ /** Canonical JSON of a manifest (contractHash excluded, keys sorted). */
27
+ export function serializeManifest(manifest: AppManifest): string {
28
+ const { contractHash: _omit, ...rest } = manifest
29
+ return `${JSON.stringify(sortValue(rest), null, 2)}\n`
30
+ }
31
+
32
+ /**
33
+ * sha256 (hex) of the canonical serialization. Async because it runs on
34
+ * WebCrypto (`globalThis.crypto.subtle`), which exists in every runtime we
35
+ * target (browsers, Node ≥ 18, edge) — no node:crypto import, keeping this
36
+ * module bundleable everywhere.
37
+ */
38
+ export async function computeContractHash(manifest: AppManifest): Promise<string> {
39
+ const bytes = new TextEncoder().encode(serializeManifest(manifest))
40
+ const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes)
41
+ return Array.from(new Uint8Array(digest))
42
+ .map((b) => b.toString(16).padStart(2, '0'))
43
+ .join('')
44
+ }
45
+
46
+ /** Serialize WITH the freshly computed contractHash embedded — what
47
+ * `fayz manifest emit` writes to app.manifest.json. */
48
+ export async function serializeManifestWithHash(manifest: AppManifest): Promise<string> {
49
+ const contractHash = await computeContractHash(manifest)
50
+ const { contractHash: _omit, ...rest } = manifest
51
+ return `${JSON.stringify(sortValue({ ...rest, contractHash }), null, 2)}\n`
52
+ }
@@ -20,6 +20,7 @@ import type {
20
20
  DashboardSurface,
21
21
  } from '../types/plugins'
22
22
  import type { FeatureDeclaration } from '../types/permissions'
23
+ import type { LimitDeclaration } from '../types/entitlements'
23
24
  import type { ConnectorDefinition } from '../integrations'
24
25
  import { getComponent } from '../registry'
25
26
 
@@ -110,6 +111,7 @@ function createEmptyRuntime(context: PluginRuntimeContext = EMPTY_CONTEXT): Plug
110
111
  registries: new Map(),
111
112
  connectorsByHost: new Map(),
112
113
  pluginFeatures: [],
114
+ pluginLimits: [],
113
115
  issues: [],
114
116
  }
115
117
  }
@@ -242,6 +244,7 @@ export function resolvePluginRuntime({
242
244
  const registries = new Map<string, PluginRegistryDef[]>()
243
245
  const connectorsByHost = new Map<string, ConnectorDefinition[]>()
244
246
  const pluginFeatures: FeatureDeclaration[] = []
247
+ const pluginLimits: LimitDeclaration[] = []
245
248
 
246
249
  for (const plugin of activePlugins) {
247
250
  routes.push(...plugin.routes.map((r) => ({ ...r, plugin })))
@@ -261,6 +264,7 @@ export function resolvePluginRuntime({
261
264
  connectorsByHost.set(connector.hostPluginId, list)
262
265
  }
263
266
  if (plugin.declaredFeatures) pluginFeatures.push(...plugin.declaredFeatures)
267
+ if (plugin.declaredLimits) pluginLimits.push(...plugin.declaredLimits)
264
268
  widgets.push(...plugin.widgets.map((w, i) => ({
265
269
  ...w,
266
270
  order: w.order ?? i,
@@ -283,7 +287,7 @@ export function resolvePluginRuntime({
283
287
  widgets.sort((a, b) => a.zone !== b.zone ? a.zone.localeCompare(b.zone) : a.order - b.order)
284
288
  dashboardWidgets.sort((a, b) => a.order - b.order)
285
289
 
286
- return { context, plugins: resolvedPlugins, activePlugins, routes, navigation, settingsTabs, widgets, dashboardWidgets, capabilities, aiTools, issues, registries, connectorsByHost, pluginFeatures }
290
+ return { context, plugins: resolvedPlugins, activePlugins, routes, navigation, settingsTabs, widgets, dashboardWidgets, capabilities, aiTools, issues, registries, connectorsByHost, pluginFeatures, pluginLimits }
287
291
  }
288
292
 
289
293
  export function getWidgetsForZone(