primitive-admin 1.1.0-alpha.75 → 1.1.0-alpha.76

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 (40) hide show
  1. package/dist/bin/primitive.js +98 -10
  2. package/dist/bin/primitive.js.map +1 -1
  3. package/dist/src/commands/functions.js +53 -18
  4. package/dist/src/commands/functions.js.map +1 -1
  5. package/dist/src/commands/prompts.js +54 -39
  6. package/dist/src/commands/prompts.js.map +1 -1
  7. package/dist/src/commands/sync.d.ts +39 -0
  8. package/dist/src/commands/sync.js +163 -90
  9. package/dist/src/commands/sync.js.map +1 -1
  10. package/dist/src/commands/workflows.js +61 -30
  11. package/dist/src/commands/workflows.js.map +1 -1
  12. package/dist/src/lib/api-client.d.ts +16 -1
  13. package/dist/src/lib/api-client.js +16 -3
  14. package/dist/src/lib/api-client.js.map +1 -1
  15. package/dist/src/lib/block-selector.d.ts +58 -0
  16. package/dist/src/lib/block-selector.js +92 -0
  17. package/dist/src/lib/block-selector.js.map +1 -0
  18. package/dist/src/lib/config-object-descriptor.js +1 -3
  19. package/dist/src/lib/config-object-descriptor.js.map +1 -1
  20. package/dist/src/lib/function-collect.d.ts +29 -7
  21. package/dist/src/lib/function-collect.js +135 -11
  22. package/dist/src/lib/function-collect.js.map +1 -1
  23. package/dist/src/lib/function-grants-preflight.d.ts +34 -88
  24. package/dist/src/lib/function-grants-preflight.js +32 -145
  25. package/dist/src/lib/function-grants-preflight.js.map +1 -1
  26. package/dist/src/lib/function-sync.d.ts +3 -14
  27. package/dist/src/lib/function-sync.js +10 -49
  28. package/dist/src/lib/function-sync.js.map +1 -1
  29. package/dist/src/lib/function-triggers.d.ts +6 -0
  30. package/dist/src/lib/function-triggers.js +63 -4
  31. package/dist/src/lib/function-triggers.js.map +1 -1
  32. package/dist/src/lib/generated-config-surfaces.d.ts +188 -236
  33. package/dist/src/lib/generated-config-surfaces.js +355 -453
  34. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  35. package/dist/src/lib/generated-sdk-types.d.ts +1 -1
  36. package/dist/src/lib/generated-sdk-types.js +1 -1
  37. package/dist/src/lib/generated-sdk-types.js.map +1 -1
  38. package/dist/src/lib/toml-database-config.js +19 -2
  39. package/dist/src/lib/toml-database-config.js.map +1 -1
  40. package/package.json +1 -1
@@ -579,39 +579,47 @@ export declare const RETIRED_CONFIG_KEYS: Record<string, Record<string, RetiredC
579
579
  /** The retired-key entry for `key` under `prefix`, or null when it is simply unknown. */
580
580
  export declare function retiredConfigKey(prefix: string, key: string): RetiredConfigKey | null;
581
581
  /**
582
- * The database capability grammar for server functions (#3182, project
583
- * `server-functions` phase 2 of the intent, phase 1 of this child).
582
+ * The capability grammar for server functions — #3182 phase 1, rewritten by
583
+ * #3279 (project `server-functions` phase 3).
584
584
  *
585
- * A function declares what it may reach in its own TOML:
585
+ * A function declares in its own TOML what it may CONFIGURE:
586
586
  *
587
- * capabilities = ["database:orders/order:read", "database:orders/order:write"]
588
- * unscopedReads = true
587
+ * capabilities = ["integration:stripe", "secret:STRIPE_KEY", "databases:delete"]
588
+ *
589
+ * ── Why the grammar is this small ────────────────────────────────────────
590
+ *
591
+ * The intent's decision (2026-09-09, "Authorization inside a function?"):
592
+ * function code acts as the system. The invocation gate is the authorization,
593
+ * and inside a function every platform call carries the app's own authority in
594
+ * every family. A capability is therefore never a statement about DATA — a
595
+ * model, a prompt, a channel, a member — because the function may reach all of
596
+ * it. It is declared only where it configures something:
597
+ *
598
+ * `integration:<key>` the egress allowlist — which upstream hosts the
599
+ * function's outbound calls may reach;
600
+ * `secret:<NAME>` credential least privilege — which secret VALUES may
601
+ * cross into the sandbox at all;
602
+ * the high-blast list {@link HIGH_BLAST_CAPABILITIES} — the operations
603
+ * whose blast radius the intent keeps opt-in.
604
+ *
605
+ * Every other string a function used to declare is RETIRED, and the grammar
606
+ * says so by name: an author who still writes `database:orders/Order:read`
607
+ * is told the model changed and what stays, not "unknown family", which would
608
+ * send them to check their spelling.
589
609
  *
590
610
  * ── Why the components have a charset ────────────────────────────────────
591
611
  *
592
- * The intent decides ("What does the database name in a grant refer to?") that
593
- * a grant names the database TYPE key, and the design fixes the string as
594
- * `database:<databaseType>/<modelName>:read|write` with no wildcards. That
595
- * string has to be INJECTIVE — one string, one (type, model, access) triple —
596
- * or a grant means two things at once. It is not injective for the identifiers
597
- * the repository accepts today: `database-type-config-controller.ts` forbids
598
- * only `#` in a type key, and js-bao's TOML loader preserves model names
599
- * verbatim, so `(type "a", model "b/c")` and `(type "a/b", model "c")` both
600
- * spell `database:a/b/c:read` (D3182-001).
601
- *
602
- * The fix is a charset rather than an escaping scheme: both components must
603
- * match `[A-Za-z0-9_-]+`, so neither delimiter can enter a component and the
604
- * grammar is injective by construction. Authored TOML stays readable, and a
605
- * database type whose key carries a delimiter is simply UNREACHABLE from
606
- * functions — fail closed, with an error that says why rather than a grant
607
- * that quietly covers a neighbour.
612
+ * A keyed grant's key must match `[A-Za-z0-9_-]+`, so neither `:` nor `/` can
613
+ * enter a component and the string is INJECTIVE — one string, one object. An
614
+ * integration or secret whose key carries a delimiter is simply unreachable
615
+ * from functions, with an error that says why (D3182-001's argument, kept).
608
616
  *
609
617
  * ── Why this module is pure ──────────────────────────────────────────────
610
618
  *
611
- * Grants are validated twice — by `config push`'s preflight, so an author sees
612
- * the error against their own file, and by the server, which is authoritative
613
- * because the raw admin API exists. Two enforcement points must not be two
614
- * grammars, so the grammar lives here, in the dependency-free
619
+ * Capabilities are validated twice — by `config push`'s preflight, so an
620
+ * author sees the error against their own file, and by the server, which is
621
+ * authoritative because the raw admin API exists. Two enforcement points must
622
+ * not be two grammars, so the grammar lives here, in the dependency-free
615
623
  * `src/config-surface/` tree the CLI vendors at build time
616
624
  * (`cli/scripts/gen-config-surfaces.mjs`). The server imports this module; the
617
625
  * CLI imports the generated copy; the drift guard fails if they differ.
@@ -621,117 +629,120 @@ export declare const GRANT_COMPONENT_PATTERN: RegExp;
621
629
  /** `ServerFunctionConfig.capabilities` is a StringSet with these bounds. */
622
630
  export declare const MAX_CAPABILITY_ENTRIES = 100;
623
631
  export declare const MAX_CAPABILITY_ENTRY_LENGTH = 200;
624
- /** The whole grammar, in one expression. */
625
- export declare const DATABASE_GRANT_PATTERN: RegExp;
626
- export type GrantAccess = "read" | "write";
627
632
  /**
628
- * The families a grant may name, in the order the guidance lists them.
629
- *
630
- * Two shapes, not one:
631
- *
632
- * KEYED `integration:<key>`, `prompt:<key>`, `secret:<NAME>`, `var:<NAME>`
633
- * — one component under {@link GRANT_COMPONENT_PATTERN}, naming a
634
- * single object. Every key format the platform issues fits: an
635
- * integration or prompt key is `^[a-z0-9][a-z0-9-_]{2,}$` and a
636
- * secret or config-var name is `^[A-Z][A-Z0-9_]{0,63}$`.
637
- * EXACT `users:send`, `connections:send` — no key component at all. The
638
- * design doc spells them this way, and the capability really is
639
- * app-wide: a send names its target at CALL time, so there is no
640
- * object for the grant to name.
641
- *
642
- * `database:` keeps its own two-component grammar, unchanged.
643
- *
644
- * `var:` is this child's choice (D3183-001). The intent is silent on config
645
- * vars; the family is named after the `{{vars.*}}` template namespace the
646
- * integration proxy already resolves, so an author who has written
647
- * `{{vars.REGION}}` reads `var:REGION` without learning a second word for the
648
- * same thing.
633
+ * The two keyed families the intent keeps: one component under
634
+ * {@link GRANT_COMPONENT_PATTERN}, naming a single object. Every key format the
635
+ * platform issues fits: an integration key is `^[a-z0-9][a-z0-9-_]{2,}$` and a
636
+ * secret name is `^[A-Z][A-Z0-9_]{0,63}$`.
649
637
  */
650
- export declare const KEYED_GRANT_FAMILIES: readonly ["integration", "prompt", "secret", "var"];
638
+ export declare const KEYED_GRANT_FAMILIES: readonly ["integration", "secret"];
651
639
  /**
652
- * Whole platform capabilities — #3187, project `server-functions` phase 4.
653
- *
654
- * The EXACT shape, for the same reason the sends take it: neither names an
655
- * object. An app has ONE hourly email budget and ONE analytics dataset, so a
656
- * grant carrying a target would be a promise the enforcement point could not
657
- * keep.
658
- *
659
- * Both gate a function-principal-only route (`emails/send`,
660
- * `analytics/write-for-user`) rather than a family the gateway passes a
661
- * caller's own authority through. `email.send` has no HTTP route for a member
662
- * to call at all, and `analytics.writeForUser` is system-only by design
663
- * (D3187-SO-001) — caller-mode family admission cannot reach either, which is
664
- * why they are grants and not passthrough.
640
+ * The high-blast-radius opt-ins — #3279, criterion 5 (CR3279-001, D3279-003).
641
+ *
642
+ * Since function code acts as the system, admission to a family is no longer
643
+ * an authority statement: everything the gateway lets through runs with the
644
+ * app's own authority. Most operations are fine that way — that is the whole
645
+ * decision. A short list is not, and the intent names its categories: delete a
646
+ * database, app or user; role changes; secret changes; resource provisioning.
647
+ * Those stay opt-in, so a function that can do them says so in a reviewable
648
+ * line of its TOML.
649
+ *
650
+ * The strings are EXACT and 1:1 with the operation id, so there is nothing to
651
+ * look up: `users.setRole` needs `users:setRole`. They parse with the verb as
652
+ * their KEY, because two exact capabilities in one family must not cover each
653
+ * other — `databases:create` is not permission to delete a database.
654
+ *
655
+ * The database ROLE mutations are here because they hand out persistent
656
+ * authority: a group grant assigns the manager role (D3279-003). App deletion
657
+ * and secret writes have no app-API route today; they are recorded, not gated,
658
+ * and the profile generator refuses to admit a future such route without a row
659
+ * here (`HIGH_BLAST_WATCH` in `scripts/lib/function-profile.mjs`).
665
660
  */
666
- export declare const PLATFORM_CAPABILITIES: readonly ["email:send", "analytics:writeForUser"];
667
- export declare const EXACT_GRANT_STRINGS: readonly ["users:send", "connections:send", "email:send", "analytics:writeForUser"];
668
- export type GrantFamily = "database" | (typeof KEYED_GRANT_FAMILIES)[number] | "users" | "connections" | "email" | "analytics";
669
- export interface DatabaseGrant {
670
- databaseType: string;
671
- modelName: string;
672
- access: GrantAccess;
673
- /** The authored string, so an error can quote what was written. */
674
- raw: string;
675
- }
661
+ export declare const HIGH_BLAST_CAPABILITIES: readonly ["databases:create", "databases:delete", "databases:transferOwnership", "databases:addManager", "databases:revokePermission", "databases:grantGroupPermission", "databases:revokeGroupPermission", "users:remove", "users:setRole", "blobBuckets:createBucket", "blobBuckets:deleteBucket"];
662
+ /** The exact strings, which since #3279 are exactly the high-blast list. */
663
+ export declare const EXACT_GRANT_STRINGS: readonly ["databases:create", "databases:delete", "databases:transferOwnership", "databases:addManager", "databases:revokePermission", "databases:grantGroupPermission", "databases:revokeGroupPermission", "users:remove", "users:setRole", "blobBuckets:createBucket", "blobBuckets:deleteBucket"];
664
+ export type GrantFamily = (typeof KEYED_GRANT_FAMILIES)[number] | "databases" | "users" | "blobBuckets";
665
+ /**
666
+ * The retired FAMILIES (#3279), each with the reason it is gone.
667
+ *
668
+ * The value is what the family used to authorize, phrased as what a function
669
+ * now reaches without it. A refusal names the authored string, says it is
670
+ * retired, gives this reason, tells the author to delete the entry, and lists
671
+ * what stays.
672
+ */
673
+ export declare const RETIRED_GRANT_FAMILIES: Record<string, string>;
674
+ /** The retired EXACT strings (#3279), on the same terms. */
675
+ export declare const RETIRED_GRANT_STRINGS: Record<string, string>;
676
+ /**
677
+ * The retirement sentence for one authored string, or null when the string is
678
+ * not a retired one.
679
+ *
680
+ * Exported so the enforcement-time reader (`loadConfigGrants`) can tell a
681
+ * retired string on a pre-change row — tolerated, logged — from a string that
682
+ * was never a grant at all, which still authorizes nothing.
683
+ */
684
+ export declare function retiredGrantRefusal(raw: string): string | null;
676
685
  /**
677
686
  * Any grant, in one flat shape.
678
687
  *
679
- * Flat rather than a discriminated union for the reason
680
- * {@link ParsedCapabilities} already gives: this module is vendored into the
681
- * CLI, which compiles with `strict: false`, so a caller reads `family` and
682
- * then the field that family populates, and the fields another family would
683
- * have used are simply absent.
688
+ * Flat rather than a discriminated union because this module is vendored into
689
+ * the CLI, which compiles with `strict: false`: a caller reads `family` and
690
+ * then `key`, and the fields another family would have used are simply absent.
684
691
  */
685
692
  export interface FunctionGrant {
686
693
  family: GrantFamily;
687
694
  /**
688
- * The single component of a keyed family — the integration key, prompt key,
689
- * secret name or config-var name. EMPTY for `database:` (which carries its
690
- * own two components) and for the exact send strings (which name nothing).
695
+ * The single component of a keyed family — the integration key or the
696
+ * secret name — or the VERB of a high-blast string (`delete` for
697
+ * `databases:delete`), so that no two exact capabilities in one family read
698
+ * as the same grant.
691
699
  */
692
700
  key: string;
693
- /** `database:` only. */
694
- databaseType?: string;
695
- modelName?: string;
696
- access?: GrantAccess;
697
701
  /** The authored string, so an error or a log line can quote it. */
698
702
  raw: string;
699
703
  }
700
- export interface ParsedGrant {
701
- grant?: DatabaseGrant;
702
- error?: string;
703
- }
704
704
  export interface ParsedFunctionGrant {
705
705
  grant?: FunctionGrant;
706
706
  error?: string;
707
707
  }
708
708
  /**
709
- * The grant string for a (type, model, access) triple, or null when either
710
- * identifier is outside the charset — which is the whole of "unreachable from
711
- * functions". Callers that hold real identifiers (the enforcement path, the
712
- * typed-handle emitter) use this rather than string concatenation, so no code
713
- * path can mint a string this module would not parse back to the same triple.
709
+ * A channel name's ceiling, and the reason it has one.
710
+ *
711
+ * The name rides in a `ConnectionMapping` row's document-id slot as
712
+ * `ch:<appId>:<channel>` and in every grant token's claims, so an unbounded
713
+ * name would be an unbounded key and an unbounded credential. 200 characters
714
+ * is {@link MAX_CAPABILITY_ENTRY_LENGTH}, kept for continuity with the rows
715
+ * #3184 already wrote.
714
716
  */
715
- export declare function formatDatabaseGrant(databaseType: string, modelName: string, access: GrantAccess): string | null;
717
+ export declare const MAX_CHANNEL_NAME_LENGTH = 200;
718
+ export interface ParsedChannelName {
719
+ /** The segment before the first `:`. Absent when the name is refused. */
720
+ namespace?: string;
721
+ error?: string;
722
+ }
716
723
  /**
717
- * One capability entry as ANY grant, or the reason it is not one.
724
+ * A channel NAME, and its namespace.
718
725
  *
719
- * The single entry point the enforcement path and both preflights use: a
720
- * caller holds one authored string and asks what it grants, without having to
721
- * know which of the families' shapes to try. Every diagnosis is
722
- * family-specific — an author who mistypes an access verb should not be told
723
- * to read a charset rule, and an author who hits the charset rule should be
724
- * told that the identifier, not their typing, is the problem.
726
+ * The grammar is the grant component charset applied per segment: one or more
727
+ * `[A-Za-z0-9_-]+` segments joined by `:`. The `channel:<namespace>` GRANT
728
+ * that used to cover a name is retired (#3279); the name grammar stays because
729
+ * the authorize and publish routes answer 400 about a name outside it before
730
+ * anything else, and the connection worker keys membership by it.
731
+ *
732
+ * Refusals name the segment that failed, because "channel name is invalid" on
733
+ * a name like `orders:a::b` tells an author nothing they cannot already see.
725
734
  */
726
- export declare function parseFunctionGrant(entry: unknown): ParsedFunctionGrant;
735
+ export declare function parseChannelName(value: unknown): ParsedChannelName;
727
736
  /**
728
- * One capability entry as a DATABASE grant, or the reason it is not one.
737
+ * One capability entry as a grant, or the reason it is not one.
729
738
  *
730
- * Kept as its own function because the `database:` grammar has two components
731
- * and an access verb, and saying which of the three went wrong is most of
732
- * what makes the error useful.
739
+ * The single entry point the enforcement path and both preflights use: a
740
+ * caller holds one authored string and asks what it grants, without having to
741
+ * know which of the shapes to try. Every diagnosis is specific — a retired
742
+ * string gets the retirement and what stays; a near miss in a kept family gets
743
+ * the family's own strings; a keyed family gets its charset rule.
733
744
  */
734
- export declare function parseDatabaseGrant(entry: unknown): ParsedGrant;
745
+ export declare function parseFunctionGrant(entry: unknown): ParsedFunctionGrant;
735
746
  /**
736
747
  * One shape rather than a discriminated union: this module is vendored into
737
748
  * the CLI, which compiles with `strict: false`, where narrowing on a boolean
@@ -742,30 +753,32 @@ export interface ParsedCapabilities {
742
753
  ok: boolean;
743
754
  /** Deduped, in authored order — what the config row stores. Empty when refused. */
744
755
  capabilities: string[];
745
- /**
746
- * DATABASE grants only, and deliberately so.
747
- *
748
- * #3182's call sites — the DO envelope, the typed handle, the records
749
- * decision, the unscoped-read rule — all read this field and all mean
750
- * "database". Widening it in place would have handed every one of them five
751
- * families it has no branch for, silently. The new families arrive beside
752
- * it in {@link allGrants}, so a caller that wants them asks for them.
753
- */
754
- grants: DatabaseGrant[];
755
- /** Every grant, all families, in authored order. */
756
+ /** Every grant, in authored order. */
756
757
  allGrants: FunctionGrant[];
757
758
  /**
758
- * The whole platform capabilities (#3187), in authored order.
759
- *
760
- * The same family projection {@link grants} is for databases: the gateway
761
- * asks "which platform capabilities does this version hold" and gets exactly
762
- * those strings, without filtering a mixed list itself.
759
+ * The retired strings that were SKIPPED, in authored order — populated only
760
+ * under `tolerateRetired` (see {@link parseCapabilities}); a strict parse
761
+ * refuses them instead and leaves this empty.
763
762
  */
764
- platformGrants: string[];
765
- hasReadGrant: boolean;
763
+ retired: string[];
766
764
  /** Empty when accepted. */
767
765
  errors: string[];
768
766
  }
767
+ export interface ParseCapabilitiesOptions {
768
+ /**
769
+ * Skip retired strings instead of refusing them — #3279 edge 25.
770
+ *
771
+ * The PUSH path is strict: a file that still declares a retired grant is
772
+ * refused with the retirement, because a line that is accepted and ignored
773
+ * is a line the author believes means something. The ENFORCEMENT path is
774
+ * tolerant: a config version pushed before #3279 carries retired strings in
775
+ * a row that can never be re-pushed to fix (its envelope is immutable), and
776
+ * refusing it there would break every function pushed before the change.
777
+ * Such a row authorizes exactly what it keeps, and the skipped strings are
778
+ * reported in `retired` so the caller can log them.
779
+ */
780
+ tolerateRetired?: boolean;
781
+ }
769
782
  /**
770
783
  * A whole `capabilities` list: shape, bounds, grammar, duplicates.
771
784
  *
@@ -773,65 +786,16 @@ export interface ParsedCapabilities {
773
786
  * field, so an over-long entry is a named push error instead of a late model
774
787
  * throw after the R2 object has already been written (principle 6).
775
788
  */
776
- export declare function parseCapabilities(value: unknown): ParsedCapabilities;
777
- /**
778
- * The unscoped-read rule, from the intent's database-authorization decision:
779
- * "a member-accessible function whose database read has no `$caller` binding
780
- * needs an explicit unscoped-read flag in its config, and that flag is the
781
- * human-reviewed artifact. From phase 2, any member-reachable function holding
782
- * a database read grant carries the flag."
783
- *
784
- * Every HTTP-invocable function is member-reachable behind its access gate, and
785
- * in this phase every function is HTTP-invocable, so the rule applies to every
786
- * config declaring a read grant — a fail-closed superset of the intent's rule,
787
- * which a later child may narrow for trigger-only functions.
788
- */
789
- export declare function unscopedReadsRefusal(grants: readonly DatabaseGrant[], unscopedReads: boolean): string | null;
789
+ export declare function parseCapabilities(value: unknown, options?: ParseCapabilitiesOptions): ParsedCapabilities;
790
790
  /**
791
- * What the two enforcement points know about one database type: the models its
792
- * schema declares, or that it declares no schema, or that the schema it does
793
- * declare could not be read.
794
- *
795
- * The third state is the reason this is an interface rather than a bare
796
- * `models` field. "No schema" and "a schema nobody can parse" are not the same
797
- * fact, and collapsing them makes an unparseable schema authorize every model
798
- * name an author cares to write (SO3182-001) — the widest possible answer to a
799
- * question the platform failed to evaluate.
800
- */
801
- export interface KnownGrantTargetType {
802
- /** Model names the schema declares, or null when it declares no schema. */
803
- models: ReadonlySet<string> | null;
804
- /** True when a stored/authored schema exists but could not be parsed. */
805
- schemaUnreadable?: boolean;
806
- }
807
- /**
808
- * What a grant may name: a database type the app has, and — when that type
809
- * declares a schema — a model the schema declares.
810
- *
811
- * `models: null` is a type with NO schema. The DatabaseDO is schemaless, so a
812
- * grant on such a type is accepted by grammar and simply carries no typed
813
- * handle; refusing it would make an untyped database ungrantable.
791
+ * The config-tree objects an `integration:` grant may name.
814
792
  *
815
- * `schemaUnreadable` is the opposite case and fails CLOSED: the type declares a
816
- * schema, the platform could not evaluate the grant against it, and an
817
- * authorization question that could not be evaluated is answered no.
818
- *
819
- * The map is supplied by the caller because the two enforcement points read
820
- * different sources for the same facts — the CLI reads the config tree's
821
- * `database-type-configs/*.toml`, the server reads the app's rows — and the
822
- * RULE is what must not differ.
823
- */
824
- export declare function validateGrantsAgainstTypes(grants: readonly DatabaseGrant[], types: ReadonlyMap<string, KnownGrantTargetType>): string[];
825
- /**
826
- * The config-tree objects an `integration:` or `prompt:` grant may name.
827
- *
828
- * Only the two families whose targets are CONFIG-TREE state. `secret:` and
829
- * `var:` are absent on purpose (D3183-002): their values are provisioned per
830
- * environment, out of band, and are not part of the reviewed tree at all, so
831
- * the ordinary order of work is to push the function and then provision the
832
- * value. Refusing an unprovisioned name at push would break that; a missing
833
- * value at CALL time is a structured runtime error instead. `users:send` and
834
- * `connections:send` name no object.
793
+ * Only the one family whose target is CONFIG-TREE state. `secret:` is absent
794
+ * on purpose (D3183-002): its values are provisioned per environment, out of
795
+ * band, and are not part of the reviewed tree at all, so the ordinary order of
796
+ * work is to push the function and then provision the value. Refusing an
797
+ * unprovisioned name at push would break that; a missing value at CALL time is
798
+ * a structured runtime error instead. The high-blast strings name no object.
835
799
  *
836
800
  * `null` means "this enforcement point could not find out". The CLI reads the
837
801
  * tree and, when it can, a live listing; when neither is available it defers
@@ -842,11 +806,9 @@ export declare function validateGrantsAgainstTypes(grants: readonly DatabaseGran
842
806
  export interface KnownGrantTargets {
843
807
  /** Non-archived integration keys, or null when unknown. */
844
808
  integrations: ReadonlySet<string> | null;
845
- /** Non-archived prompt keys, or null when unknown. */
846
- prompts: ReadonlySet<string> | null;
847
809
  }
848
810
  /**
849
- * Grants naming an integration or prompt the app does not have.
811
+ * Grants naming an integration the app does not have.
850
812
  *
851
813
  * One message per offending grant, quoting the whole authored string AND the
852
814
  * key on its own, so an operator reading the line knows both what to fix in
@@ -868,23 +830,20 @@ export declare function validateKeyedGrantsAgainstTargets(grants: readonly Funct
868
830
  *
869
831
  * ── What the manifest is for ─────────────────────────────────────────────
870
832
  *
871
- * Two things, and neither is authorization at runtime:
872
- *
873
- * 1. **The read rule.** The intent's database-authorization decision says
874
- * "from phase 4 a recorded `$caller` binding on every read satisfies the
875
- * [unscoped-read] rule instead". That decision is made ONCE, at push, by
876
- * a human-reviewable statement — which is exactly what a manifest is.
877
- * 2. **Observability.** `primitive functions get` and the admin get list
878
- * what a version registered, so an operator can see it without reading
879
- * the bundle (principle 8).
833
+ * Observability, and nothing else. `primitive functions get` and the admin
834
+ * get list what a version registered, so an operator can see it without
835
+ * reading the bundle (principle 8). The read rule it used to be decided from
836
+ * (#3187's `$caller` relaxation of `unscopedReads`) is retired by #3279:
837
+ * function code acts as the system, so there is no per-model grant for a
838
+ * binding to waive. The intent says so in as many words — "the models a
839
+ * function touches are collected at push as a reviewable manifest, not an
840
+ * authorization".
880
841
  *
881
842
  * It is HONEST-CODE evidence and the design doc says so: a hostile bundle can
882
843
  * register whatever it likes, because the collector runs the tenant's own
883
- * code. Nothing security-relevant at runtime reads it — parameter injection,
884
- * the raw-read refusal and cache verification all happen in the platform-owned
885
- * SDK inside the isolate, and the grants remain the adversarial boundary. What
886
- * the manifest buys is protection against BUGS, which is the bound the design
887
- * doc states and this comment repeats so nobody has to rediscover it.
844
+ * code. Nothing security-relevant at runtime reads it — parameter injection
845
+ * and cache verification happen in the platform-owned SDK inside the isolate,
846
+ * and the invocation gate remains the adversarial boundary.
888
847
  *
889
848
  * ── Why the grammar is here ──────────────────────────────────────────────
890
849
  *
@@ -893,11 +852,23 @@ export declare function validateKeyedGrantsAgainstTargets(grants: readonly Funct
893
852
  * authoritatively because the raw admin API exists. Two enforcement points,
894
853
  * one rule, in the dependency-free tree the CLI vendors.
895
854
  */
896
- /** The manifest encoding, versioned with the envelope that carries it. */
897
- export declare const FUNCTION_MANIFEST_SCHEMA_VERSION = 1;
855
+ /**
856
+ * The manifest encoding, versioned with the envelope that carries it.
857
+ *
858
+ * Version 2 (#3279 behavior 10) adds what a function TOUCHES beside what it
859
+ * registers: the deduplicated `models` its code names and the `families` of
860
+ * `ctx.api` and the ctx helpers it reaches, collected by a static scan of the
861
+ * built bundle, plus a `dynamicModels` marker for a model name the scan
862
+ * could not resolve — distinct from an empty list, which means "names none".
863
+ * A version-1 manifest still parses: it records registrations only.
864
+ */
865
+ export declare const FUNCTION_MANIFEST_SCHEMA_VERSION = 2;
866
+ export declare const FUNCTION_MANIFEST_SCHEMA_VERSIONS: readonly [1, 2];
898
867
  /** Bounds, so a manifest cannot be a way to store an unbounded blob. */
899
868
  export declare const MAX_MANIFEST_QUERIES = 200;
900
869
  export declare const MAX_MANIFEST_NAME_LENGTH = 120;
870
+ export declare const MAX_MANIFEST_MODELS = 200;
871
+ export declare const MAX_MANIFEST_FAMILIES = 40;
901
872
  export interface ManifestParam {
902
873
  type?: string;
903
874
  caller?: boolean;
@@ -918,6 +889,21 @@ export interface ManifestQuery {
918
889
  export interface FunctionManifest {
919
890
  schemaVersion: number;
920
891
  queries: ManifestQuery[];
892
+ /**
893
+ * Every model name the bundle names, deduplicated and sorted: registration
894
+ * declarations, literal `.model("…")` calls, and literal `modelName`/`model`
895
+ * arguments of the direct records and documents calls (#3279). Empty for a
896
+ * version-1 manifest, and for a function that names none.
897
+ */
898
+ models: string[];
899
+ /** Every `ctx.api.<family>` and ctx-helper family the bundle reaches. */
900
+ families: string[];
901
+ /**
902
+ * The scan met a model name it could not resolve — a computed
903
+ * `modelName`, a `.model(variable)`. Says "and possibly more", which an
904
+ * empty `models` list does not.
905
+ */
906
+ dynamicModels: boolean;
921
907
  }
922
908
  export interface ParsedManifest {
923
909
  ok: boolean;
@@ -932,40 +918,6 @@ export interface ParsedManifest {
932
918
  * and whether the author meant any of it are questions this cannot answer.
933
919
  */
934
920
  export declare function parseFunctionManifest(value: unknown): ParsedManifest;
935
- /**
936
- * The phase-4 relaxation of the unscoped-read rule, from the intent: "from
937
- * phase 4 a recorded `$caller` binding on every read satisfies the rule
938
- * instead".
939
- *
940
- * Returns the refusal, or null when the config may be pushed without
941
- * `unscopedReads = true`.
942
- *
943
- * Fail-closed in three ways, each of which is a way an author could otherwise
944
- * get the flag waived without the property it stands for:
945
- *
946
- * - no manifest at all is no evidence, so the flag is still required;
947
- * - a manifest naming NO query that touches a read-granted model records
948
- * nothing about how those models are read, so it satisfies nothing;
949
- * - one non-caller-scoped registration over a read-granted model is enough
950
- * to require the flag, because that query reads every row for every
951
- * caller — which is exactly what the flag declares.
952
- */
953
- export declare function callerScopedReadsRefusal(grants: readonly DatabaseGrant[], unscopedReads: boolean, manifest: FunctionManifest | null): string | null;
954
- /**
955
- * Whether a running version's raw (non-query) reads must be refused inside the
956
- * sandbox — #3187 behavior 23.
957
- *
958
- * True exactly when the version was accepted under the relaxation: it holds a
959
- * database READ grant and did not declare `unscopedReads`. Such a version was
960
- * pushed on the strength of its manifest saying every read is caller-scoped,
961
- * so the SDK admits the caller-scoped registered reads and refuses the rest.
962
- *
963
- * Honest-code protection at the design doc's stated adequacy: the SDK is
964
- * platform-owned, but a hostile bundle is not obliged to use it. The gateway
965
- * and DatabaseDO grant checks (#3182) are unchanged and remain the adversarial
966
- * boundary.
967
- */
968
- export declare function callerScopedReadsRequired(capabilities: readonly string[], unscopedReads: boolean): boolean;
969
921
  export declare const WORKFLOW_SURFACE: ConfigObjectSurface;
970
922
  export declare const PROMPT_SURFACE: ConfigObjectSurface;
971
923
  export declare const INTEGRATION_SURFACE: ConfigObjectSurface;