@aotter/mantle 0.2.0 → 0.2.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/build/compiler.d.ts +4 -4
- package/dist/build/compiler.js +6 -3
- package/dist/build/spec/domain/model/ManifestGrammar.d.ts +39 -1
- package/dist/build/spec/domain/model/RuntimePlan.d.ts +16 -3
- package/dist/build/spec/domain/model/SqlIr.d.ts +30 -0
- package/dist/build/spec/kernel/diagnostic.d.ts +1 -1
- package/dist/domain/describe/chunk-P6PP6VVV.js +1 -0
- package/dist/domain/describe/index.d.ts +1 -0
- package/dist/domain/describe/index.js +1 -1
- package/dist/domain/describe/match.d.ts +19 -0
- package/dist/domain/describe/target-hash.js +1 -1
- package/dist/infrastructure/adminEntries.js +19 -3
- package/dist/infrastructure/bundle.js +43 -8
- package/dist/infrastructure/cli.js +1 -1
- package/dist/infrastructure/generate.js +17 -3
- package/dist/kernel/binds.d.ts +18 -3
- package/dist/kernel/binds.js +1 -1
- package/dist/kernel/contracts.d.ts +7 -0
- package/dist/kernel/contracts.js +9 -0
- package/dist/kernel/driver/bun-sqlite.js +3 -3
- package/dist/kernel/driver/d1.d.ts +1 -1
- package/dist/kernel/driver/d1.js +6 -3
- package/dist/kernel/driver/postgres.d.ts +2 -1
- package/dist/kernel/driver/postgres.js +11 -6
- package/dist/kernel/mcp-handler.js +2 -1
- package/dist/kernel/rest-router.js +3 -5
- package/dist/kernel/rest.d.ts +2 -1
- package/dist/kernel/rest.js +1 -0
- package/dist/kernel/route-pattern.d.ts +26 -0
- package/dist/kernel/route-pattern.js +34 -0
- package/dist/kernel/store.d.ts +15 -1
- package/dist/kernel/store.js +51 -18
- package/dist/usecase/adminBindings.js +2 -2
- package/dist/usecase/adminClient.js +4 -1
- package/dist/usecase/generate.js +7 -5
- package/dist/usecase/handlers.js +1 -1
- package/dist/usecase/publishing.js +20 -7
- package/dist/usecase/rest.js +5 -5
- package/dist/usecase/stores.js +8 -3
- package/docs/examples/README.md +1 -1
- package/docs/examples/app-shell/README.md +58 -10
- package/docs/examples/app-shell/package.json +6 -6
- package/docs/examples/club-events/manifests/club-events.yaml +2 -0
- package/docs/examples/generated-web/seed.sql +5 -4
- package/docs/examples/purchasing/manifests/purchasing.yaml +3 -0
- package/docs/handbook/cloudflare/media-r2.md +6 -3
- package/docs/handbook/cloudflare/service-entry.md +2 -0
- package/docs/handbook/concepts/lifecycle-and-locales.md +21 -1
- package/docs/handbook/examples/hub.md +1 -1
- package/docs/handbook/guides/admin-ui.md +1 -1
- package/docs/handbook/guides/agent-setup.md +13 -8
- package/docs/handbook/guides/agent-workflow-acceptance.md +129 -19
- package/docs/handbook/guides/local-testing.md +50 -5
- package/docs/handbook/guides/native-admin.md +218 -11
- package/docs/handbook/guides/native-execution.md +5 -4
- package/docs/handbook/guides/revisions.md +584 -0
- package/docs/handbook/guides/size-and-startup.md +140 -0
- package/docs/handbook/guides/sql-patterns.md +47 -1
- package/docs/handbook/guides/typed-queries.md +8 -3
- package/docs/handbook/navigation.json +4 -0
- package/docs/handbook/reference/authorization.md +2 -0
- package/docs/handbook/reference/describe.md +42 -0
- package/docs/handbook/reference/diagnostics.md +12 -3
- package/docs/handbook/reference/procedure.md +4 -4
- package/docs/handbook/reference/schema.md +71 -7
- package/docs/handbook/reference/sql.md +2 -1
- package/docs/handbook/reference/surface.md +3 -2
- package/docs/handbook/reference/trigger.md +12 -0
- package/docs/handbook/releases/index.md +44 -1
- package/docs/handbook/start/agents.md +6 -2
- package/docs/handbook/start/project-and-cli.md +4 -0
- package/docs/handbook/start/quickstart-worker.md +1 -1
- package/docs/skills/develop/SKILL.md +15 -3
- package/docs/skills/integrate/SKILL.md +3 -1
- package/docs/skills/performance/SKILL.md +169 -0
- package/docs/skills/update/SKILL.md +9 -5
- package/docs/upgrade-0.1-to-0.2.md +151 -39
- package/package.json +9 -9
- package/skills/README.md +6 -2
- package/skills/mantle/SKILL.md +19 -5
- package/dist/domain/describe/chunk-SGMRVDGE.js +0 -1
package/skills/README.md
CHANGED
|
@@ -18,6 +18,7 @@ distributions (ADR-0032 decision 13):
|
|
|
18
18
|
| [`update`](../docs/skills/update/SKILL.md) | Upgrade `@aotter/mantle`, including from 0.1.x. |
|
|
19
19
|
| [`provision`](../docs/skills/provision/SKILL.md) | Ship to Cloudflare Workers or ChatGPT Sites with production sign-in. |
|
|
20
20
|
| [`media-gc`](../docs/skills/media-gc/SKILL.md) | Audit or remove stale, uncommitted R2 uploads. |
|
|
21
|
+
| [`performance`](../docs/skills/performance/SKILL.md) | Make an accepted app fast: Lighthouse, cold start, backend load, indexes and Views. Runs after acceptance converges. |
|
|
21
22
|
|
|
22
23
|
## Install
|
|
23
24
|
|
|
@@ -27,10 +28,12 @@ release tag as the application's SDK; alpha releases on `develop` can be newer
|
|
|
27
28
|
than the default `main` branch. For a skill-only installation:
|
|
28
29
|
|
|
29
30
|
```sh
|
|
30
|
-
npx skills add
|
|
31
|
+
npx skills add aotter/mantle@mantle
|
|
31
32
|
```
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
Without a version it follows `main`, the latest stable release; pin the SDK's
|
|
35
|
+
release tag with `npx skills add aotter/mantle#v0.2.1-alpha.1@mantle`.
|
|
36
|
+
The explicit skill name (`@mantle`) excludes repository maintainer skills. The manifests
|
|
34
37
|
are generated by `scripts/sync-plugin-manifests.mjs` and checked with `--check`.
|
|
35
38
|
When upgrading, update the marketplace ref or local tagged checkout and refresh
|
|
36
39
|
the plugin. Verify the loaded manifest version; Claude Code's plugin version
|
|
@@ -53,6 +56,7 @@ each other: `metadata.projection` is `plugin` for `skills/mantle` and
|
|
|
53
56
|
| `update` | an SDK upgrade | never overwrite application files; 0.1.x moves by the upgrade guide to a new database | package | — |
|
|
54
57
|
| `provision` | ship to production | secrets never in source, chat or logs; a new database for 0.2; self-hosting is always an option | package | Creates remote resources and handles production secrets; opt-in only. |
|
|
55
58
|
| `media-gc` | stale R2 uploads | audit by default; exact keys only, never a prefix delete; re-audit before applying | package | Destructive remote object deletion and Cloudflare-specific; opt-in only. |
|
|
59
|
+
| `performance` | converged acceptance, a slow page, first request or query | accepted behavior only; never trade scope, authorization, OCC or atomicity for speed; local unless deployment is authorized | package | — |
|
|
56
60
|
|
|
57
61
|
## Audience
|
|
58
62
|
|
package/skills/mantle/SKILL.md
CHANGED
|
@@ -38,6 +38,7 @@ lacks a guide named here, record the missing guidance and keep that version's co
|
|
|
38
38
|
- Read `node_modules/@aotter/mantle/docs/skills/update/SKILL.md` to upgrade Mantle, including from 0.1.x.
|
|
39
39
|
- Read `node_modules/@aotter/mantle/docs/skills/provision/SKILL.md` only for an authorized production deployment.
|
|
40
40
|
- Read `node_modules/@aotter/mantle/docs/skills/media-gc/SKILL.md` only to audit or remove stale uncommitted media uploads.
|
|
41
|
+
- Read `node_modules/@aotter/mantle/docs/skills/performance/SKILL.md` after acceptance converges, or when a page, first request or query is slow.
|
|
41
42
|
|
|
42
43
|
## New application
|
|
43
44
|
|
|
@@ -50,7 +51,7 @@ includes:
|
|
|
50
51
|
- Real business rules in SQL Procedures (`mantle help sql-patterns`), not in handler code around separate writes.
|
|
51
52
|
- For visitor pages, everything in `mantle help public-pages` (SEO, AEO, sitemap, robots, `llms.txt`), styled.
|
|
52
53
|
- Local HTTP checks (`mantle help local-testing`), then independent acceptance with `pnpm accept`
|
|
53
|
-
(`mantle help agent-workflow-acceptance`).
|
|
54
|
+
(`mantle help agent-workflow-acceptance`), then the installed `docs/skills/performance/SKILL.md`.
|
|
54
55
|
|
|
55
56
|
Take patterns from `mantle help examples` into the app's own manifests and `src/`; do not copy an example directory
|
|
56
57
|
over the app. Resolve each `AGENT:` or name the ones you left. Generation only, inside an app you already have: skip
|
|
@@ -59,8 +60,19 @@ over the app. Resolve each `AGENT:` or name the ones you left. Generation only,
|
|
|
59
60
|
## Accept before reporting done
|
|
60
61
|
|
|
61
62
|
Before reporting any new app or feature as done, follow `mantle help agent-workflow-acceptance`: a private rubric,
|
|
62
|
-
then one fresh agent per role given only its role and the URLs `pnpm accept` prints, at most three rounds.
|
|
63
|
-
|
|
63
|
+
then one fresh agent per role given only its role and the URLs `pnpm accept` prints, at most three rounds. The first
|
|
64
|
+
delivery covers every role and surface; after a repair, the guide's "Re-verify what a repair touches" rule decides what
|
|
65
|
+
re-runs: a page-only repair re-runs the affected UI, while auth, scope, SQL Procedure, unique index or shared-transport changes widen
|
|
66
|
+
to every surface they reach. State the round and revision of any evidence you reuse. If it was
|
|
67
|
+
not run, report the work as UNVERIFIED rather than done; if it could not run, BLOCKED. Report status per layer as that
|
|
68
|
+
guide's "Report done-ness in layers" defines; a block from your environment (approval policy, missing browser,
|
|
69
|
+
network) is BLOCKED on its layer, not a product failure, and does not discard the other layers.
|
|
70
|
+
|
|
71
|
+
Once acceptance is CONVERGED, read the installed `docs/skills/performance/SKILL.md`: it decides which of front end,
|
|
72
|
+
cold start and backend load apply, and runs them before you report done. This is the one exception to installed docs
|
|
73
|
+
winning: if the installed package predates that skill, read
|
|
74
|
+
[the 0.2.1 copy](https://github.com/aotter/mantle/blob/v0.2.1/docs/skills/performance/SKILL.md) and the pages it links
|
|
75
|
+
from the installed docs with `mantle help <topic>`.
|
|
64
76
|
|
|
65
77
|
## Deployment
|
|
66
78
|
|
|
@@ -72,5 +84,7 @@ host's native workflow. Local project creation alone is not deployment authoriza
|
|
|
72
84
|
Preserve the user's manifests, `src/` files, identity, package versions and provider resources. Run the project's
|
|
73
85
|
generate command with `--check` and its own tests before and after a change.
|
|
74
86
|
|
|
75
|
-
Report the project path, the exact Mantle version, what you changed, the
|
|
76
|
-
independent acceptance verdict
|
|
87
|
+
Report the project path, the exact Mantle version, what you changed, the four-layer status with each layer's checks
|
|
88
|
+
and results (the independent acceptance verdict, CONVERGED / BLOCKED / UNVERIFIED, is layer 3), the performance verdict
|
|
89
|
+
(PASS / PARTIAL / SKIPPED) with its before and after numbers, and anything left for the user, including what would
|
|
90
|
+
unblock a BLOCKED layer.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function resolveLocalizedText(value,preferred,canonical2){if(value==null)return null;if(typeof value=="string")return value;if(Object.prototype.hasOwnProperty.call(value,preferred))return value[preferred];if(canonical2&&Object.prototype.hasOwnProperty.call(value,canonical2))return value[canonical2];let firstKey=Object.keys(value)[0];return firstKey!==void 0?value[firstKey]:null}var INPUT_RULE_KEYWORDS=["dependentRequired","anyOf","if","then","else"];function lookupLocalRef(root,ref){if(typeof ref!="string"||!ref.startsWith("#/"))return;let value=root;try{for(let key of decodeURIComponent(ref.slice(2)).split("/").map(key2=>key2.replace(/~1/g,"/").replace(/~0/g,"~")))value=value&&typeof value=="object"&&Object.hasOwn(value,key)?value[key]:void 0}catch{return}return value&&typeof value=="object"?value:void 0}function rootRefChain(input){let chain=[];for(let schema=input;schema!==void 0;schema=lookupLocalRef(input,schema.$ref)){if(chain.includes(schema))return;if(chain.push(schema),schema.$ref===void 0)return chain}}function mcpToolNameSegment(name){return name.toLowerCase().replace(/-/g,"_")}function toolTitle(name){let words=name.replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/[-_.\s]+/g," ").trim().toLowerCase();return words.charAt(0).toUpperCase()+words.slice(1)}var viewAnnotations={readOnlyHint:!0,destructiveHint:!1};function procedureAnnotations(p){let readOnlyHint=p.mcp?.readOnlyHint??!1;return{readOnlyHint,destructiveHint:!readOnlyHint&&("ref"in p.handler||overwrites(p.handler.sql.stmts)),...p.mcp}}var overwrites=node=>!!node&&typeof node=="object"&&("DeleteStmt"in node||"UpdateStmt"in node||node.onConflictClause?.action==="ONCONFLICT_UPDATE"||Object.values(node).some(overwrites));function mcpTools(plan,surface,locale="en"){let text=t=>resolveLocalizedText(t,locale,"en")??void 0,tools=new Map,add=tool=>{let prior=tools.get(tool.name);if(prior&&(prior.kind!==tool.kind||prior.source!==tool.source))throw new TypeError(`MCP tool '${tool.name}' comes from both '${prior.source}' and '${tool.source}'.`);tools.set(tool.name,tool)};for(let t of Object.values(plan.triggers)){if(t.source.kind!=="mcp"||t.source.surface!==surface)continue;let p=plan.procedures[t.procedure],title=text(p.title);add({name:mcpToolNameSegment(t.procedure),kind:"procedure",source:t.procedure,title:title??toolTitle(t.procedure),description:text(p.description)??title??t.procedure,inputSchema:p.input,...p.output.type==="object"?{outputSchema:p.output}:{},annotations:procedureAnnotations(p),requires:p.requires})}for(let[name,v]of Object.entries(plan.views)){if(v.surface!==surface)continue;let chain=(v.input?rootRefChain(v.input)??[v.input]:[]).reverse(),props=Object.assign({},...chain.map(s=>s.properties??{})),required=[...new Set(chain.flatMap(s=>s.required??[]))];if("limit"in props||"cursor"in props)throw new TypeError(`View '${name}' declares an input named limit or cursor; the MCP tool reserves both for paging.`);let rules=chain.map(s=>Object.fromEntries(INPUT_RULE_KEYWORDS.flatMap(k=>s[k]===void 0?[]:[[k,s[k]]]))).filter(r=>Object.keys(r).length),defs=v.input?referencedDefs(v.input,props):{},title=text(v.title);add({name:mcpToolNameSegment(name),kind:"view",source:name,title:title??toolTitle(name),description:text(v.description)??title??name,inputSchema:{type:"object",properties:{...props,limit:{type:"integer",minimum:1,maximum:500},cursor:{type:"string"}},...required.length?{required}:{},...rules.length>1?{allOf:rules}:rules[0],...Object.keys(defs).length?{$defs:defs}:{}},annotations:viewAnnotations,requires:v.requires})}return[...tools.values()]}function referencedDefs(root,node,found={}){if(!node||typeof node!="object")return found;let ref=node.$ref,[head,segment]=typeof ref=="string"&&ref.startsWith("#/")?decoded(ref.slice(2)).split("/"):[],key=head==="$defs"?segment?.replace(/~1/g,"/").replace(/~0/g,"~"):void 0;key!==void 0&&!Object.hasOwn(found,key)&&root.$defs&&Object.hasOwn(root.$defs,key)&&(found[key]=root.$defs[key],referencedDefs(root,found[key],found));for(let child of Object.values(node))referencedDefs(root,child,found);return found}var decoded=pointer=>{try{return decodeURIComponent(pointer)}catch{return""}};function canonical(v){return Array.isArray(v)?`[${v.map(canonical).join(",")}]`:v&&typeof v=="object"?`{${Object.keys(v).sort().filter(k=>v[k]!==void 0).map(k=>`${JSON.stringify(k)}:${canonical(v[k])}`).join(",")}}`:JSON.stringify(v)??"null"}var publishingTransitions=[["publish",["draft"],"published"],["unpublish",["published","archived"],"draft"],["archive",["draft","published"],"archived"]];var publishingInput={type:"object",properties:{id:{type:"string",minLength:1},expectedVersion:{type:"integer",minimum:1}},required:["id","expectedVersion"],additionalProperties:!1};function mediaDefinitions(){let define=(name,description,inputSchema,action,readOnlyHint=!1)=>({name,description,inputSchema,action,annotations:{readOnlyHint,destructiveHint:action==="delete",openWorldHint:!0}}),string={type:"string"},texts={alt:{type:"string",maxLength:1e3},caption:{type:"string",maxLength:1e3}},input=(properties,required=[])=>({type:"object",properties,required,additionalProperties:!1});return[define("get_media_upload_policy","Read declared image purpose MIME slots and byte caps before preparing variants or requesting an upload. Optimization runs locally, never in the host.",input({}),"policy",!0),define("create_media_upload","Create short-lived PUT capabilities for image variants. Read site media purpose policy first; optimize images locally. Obtain attachment bytes yourself, then PUT each variant with its requiredHeaders and call commit_media_upload. Never store upload URLs as published images or ask the user to run terminal commands.",input({filename:{type:"string",maxLength:255},purpose:string,variants:{type:"array",minItems:1,items:input({mimeType:string,byteSize:{type:"integer",minimum:1},role:{type:"string",enum:["primary","alternate","fallback"]}},["mimeType","byteSize","role"])},...texts},["filename","purpose","variants"]),"create"),define("commit_media_upload","Verify all uploaded variants and commit one permanent media asset. Store the returned asset id in fields referencing media_assets.id, never a temporary upload URL. Unknown or expired upload groups require a new create_media_upload.",input({uploadGroupId:string,...texts},["uploadGroupId"]),"commit"),define("list_media_assets","List committed image assets, newest first, with their permanent variants and alt/caption. Search matches id, alt or caption; reuse only a returned cursor.",input({limit:{type:"integer",minimum:1,maximum:500},cursor:string,search:string}),"list",!0),define("get_media_asset","Read one committed media asset and its permanent variant URLs.",input({id:string},["id"]),"get",!0),define("update_media_asset","Update committed asset alt or caption; omitted fields remain unchanged and empty strings clear text.",input({id:string,...texts},["id"]),"update"),define("delete_media_asset","Delete an asset's objects and library record. Check application references first. A partial failure can be retried using the same id.",input({id:string},["id"]),"delete")]}function inspectSql(value){let schemas=new Set,readSchemas=new Set,writes=[],readOnly=!0,walk=v=>{if(!v||typeof v!="object")return;let n=v;n.RangeVar?.mantle==="table"&&(schemas.add(n.RangeVar.relname.toLowerCase()),readSchemas.add(n.RangeVar.relname.toLowerCase()));for(let kind of["InsertStmt","UpdateStmt","DeleteStmt"])n[kind]&&(readOnly=!1,n[kind].relation?.relname&&writes.push({schema:n[kind].relation.relname.toLowerCase(),operation:kind==="InsertStmt"?"create":kind==="UpdateStmt"?"update":"delete"}),n[kind].relation?.relname&&(schemas.add(n[kind].relation.relname.toLowerCase()),kind!=="InsertStmt"&&readSchemas.add(n[kind].relation.relname.toLowerCase())));Object.values(n).forEach(walk)};return walk(value),{schemas:[...schemas].sort(),readSchemas:[...readSchemas].sort(),readOnly,writes}}function mergeRestrict(policies,conjunctive){return Object.fromEntries([...new Set(policies.flatMap(p=>Object.keys(p)))].sort().map(schema=>{let rows=policies.map(p=>p[schema]??{terms:[]}),terms=[...new Map(rows.flatMap(p=>p.terms).map(t=>[canonical(t),t])).values()].filter(t=>conjunctive||rows.every(p=>p.terms.some(v=>canonical(v)===canonical(t)))),owner=rows.find(p=>p.owner)?.owner;return[schema,{...owner&&(conjunctive||rows.every(p=>p.owner===owner))?{owner}:{},terms}]}))}function inspectBehavior(plan,ir,opaque,requires,restrict,writes){let inspected=inspectSql(ir);writes&&(inspected.writes=[...writes],inspected.schemas.push(...writes.map(w=>w.schema)),inspected.readSchemas.push(...writes.filter(w=>w.operation!=="create").map(w=>w.schema)));let dependencies=[],policies=[restrict??{}],procedures=new Set,hooks=new Set,visit=name=>{if(procedures.has(name))return;procedures.add(name);let p=plan.procedures[name];if(!p)throw new TypeError(`Unknown dependency '${name}'.`);dependencies.push({procedure:name,declaration:p}),policies.push(p.restrict??{}),opaque||="ref"in p.handler;let g=inspectSql("sql"in p.handler?p.handler.sql.stmts:void 0);inspected.schemas.push(...g.schemas),inspected.readSchemas.push(...g.readSchemas),inspected.writes.push(...g.writes),inspected.readOnly&&=g.readOnly,p.requires?.guard&&visit(p.requires.guard.procedure)};for(requires?.guard&&visit(requires.guard.procedure);;){let before=hooks.size;for(let[id,t]of Object.entries(plan.triggers).sort(([a],[b])=>a<b?-1:a>b?1:0)){if(t.source.kind!=="lifecycle"||hooks.has(id))continue;let source=t.source;!opaque&&!inspected.writes.some(w=>w.schema===source.schema.toLowerCase()&&source.on.some(on=>on.endsWith(`_${w.operation}`)))||(hooks.add(id),dependencies.push({trigger:id,source}),visit(t.procedure))}if(hooks.size===before)break}let related=names=>{let all=new Set(opaque?Object.keys(plan.schemas):names);for(let name of all){let parent=plan.schemas[name]?.translates?.parent.toLowerCase();parent&&all.add(parent)}return[...all].sort()};return{...inspected,schemas:related(inspected.schemas),readSchemas:related(inspected.readSchemas),opaque,restrict:mergeRestrict(policies,!0),dependencies}}var objectInput={type:"object",properties:{}},pathTemplate=path=>path.replace(/\{([^}]+)\}/g,":$1"),adminRoutes=["GET /me","GET /site","GET /collections","GET /views-manifest","GET /operations","GET /webmcp","POST /webmcp/:tool","GET /bootstrap","GET /views/:name/export","GET /views/:name","GET /view-batch","GET /developer/views/:name","POST /operations/:name","GET /developer-console","GET /collections/:name/statistics","GET /entries","GET /entries/export","GET /entries/:id","POST /entries","PATCH /entries/:id","POST /entries/:id/publish","POST /entries/:id/unpublish","POST /entries/:id/archive","DELETE /entries/:id","GET /site-settings","PATCH /site-settings","POST /media/uploads","POST /media/uploads/:groupId/commit","GET /media","GET /media/:id","PATCH /media/:id","DELETE /media/:id","GET /admin","GET /members","PATCH /admin/:id/role","POST /admin/invitations","DELETE /admin/invitations/:id"];function targetDefinitions(plan){let out=[],tools=[...mcpTools(plan,"public"),...mcpTools(plan,"admin")];for(let[kind,entries]of[["view",plan.views],["procedure",plan.procedures]])for(let[name,p]of Object.entries(entries).sort(([a],[b])=>a<b?-1:a>b?1:0)){let view=kind==="view"?plan.views[name]:void 0,proc=kind==="procedure"?plan.procedures[name]:void 0,ir=view?.stmts??(proc&&"sql"in proc.handler?proc.handler.sql.stmts:void 0),inspected=inspectBehavior(plan,ir,!!proc&&"ref"in proc.handler,p.requires,p.restrict),tool=tools.find(t=>t.kind===kind&&t.source===name),title=resolveLocalizedText(p.title,"en","en")??name,triggers=Object.entries(plan.triggers).filter(([,t])=>kind==="procedure"&&t.procedure===name).sort(([a],[b])=>a<b?-1:a>b?1:0).map(([id,t])=>({id,source:t.source})),surfaces=[...new Set([...view?[view.surface,"admin",...view.surface==="public"?["rest"]:[]]:[],...triggers.map(t=>t.source.kind==="mcp"?t.source.surface:t.source.kind==="http"?"rest":t.source.kind)])].sort();surfaces.length||surfaces.push("internal"),out.push({id:`${kind}:${name}`,name:tool?.name??mcpToolNameSegment(name),kind,surface:view?.surface??(surfaces.includes("public")||surfaces.includes("rest")?"public":surfaces.includes("admin")?"admin":"internal"),surfaces,triggers,title,description:resolveLocalizedText(p.description,"en","en")??title,inputSchema:tool?.inputSchema??view?.input??proc?.input??objectInput,annotations:tool?.annotations??{},requires:p.requires,restrict:inspected.restrict,schemas:inspected.schemas,readSchemas:inspected.readSchemas,readOnly:!inspected.opaque&&inspected.readOnly,opaque:inspected.opaque,trustedOnly:!!proc?.trustedOnly,variants:[],behavior:{declaration:p,dependencies:inspected.dependencies}})}let addRoute=(id,dependencies,inputSchema=objectInput)=>{let admin=id.startsWith("admin:");out.push({id,name:id.replace(/[^a-zA-Z0-9_-]/g,"_"),title:id,description:id,kind:admin?"admin":"rest",surface:admin?"admin":"public",surfaces:[admin?"admin":"rest"],triggers:dependencies.flatMap(d=>d.triggers),inputSchema,annotations:{},requires:admin?{auth:{all:["ctx.admin"]}}:dependencies[0]?.requires,restrict:mergeRestrict(dependencies.map(d=>d.restrict),!1),schemas:[...new Set(dependencies.flatMap(d=>d.schemas))].sort(),readSchemas:[...new Set(dependencies.flatMap(d=>d.readSchemas))].sort(),readOnly:dependencies.length>0&&dependencies.every(d=>d.readOnly)&&(!admin||id.startsWith("admin:GET ")||id==="admin:POST /operations/:name"||id==="admin:POST /webmcp/:tool"),opaque:dependencies.some(d=>d.opaque),trustedOnly:dependencies.some(d=>d.trustedOnly),variants:dependencies,behavior:dependencies.map(d=>({id:d.id,behavior:d.behavior,surfaces:d.surfaces,triggers:d.triggers,input:d.inputSchema,requires:d.requires,restrict:d.restrict,trustedOnly:d.trustedOnly}))})},declarations=[...out],adminOperations=new Set(mcpTools(plan,"admin").filter(t=>t.kind==="procedure").map(t=>`procedure:${t.source}`));for(let d of declarations)d.kind==="view"&&d.surfaces.includes("public")&&addRoute(`rest:GET /api/views/${d.id.slice(5)}`,[d],d.inputSchema);for(let[,t]of Object.entries(plan.triggers).sort(([a],[b])=>a<b?-1:a>b?1:0))if(t.source.kind==="http"){let d=declarations.find(d2=>d2.id===`procedure:${t.procedure}`);addRoute(`rest:${t.source.method} ${pathTemplate(t.source.path)}`,[d],d.inputSchema)}let generated=[];for(let[schema,s]of Object.entries(plan.schemas).sort(([a],[b])=>a<b?-1:a>b?1:0))if(s.publishing)for(let[verb,from,to]of publishingTransitions){addRoute(`procedure:${s.name}.${verb}`,[],publishingInput);let d=out.at(-1),inspected=inspectBehavior(plan,void 0,!1,void 0,void 0,[{schema,operation:verb==="publish"?"publish":"update"}]);Object.assign(d,{kind:"procedure",name:mcpToolNameSegment(`${verb}_${s.name}`),surface:"admin",surfaces:["admin"],requires:{auth:{all:["ctx.admin"]}},schemas:[...new Set([schema,...inspected.schemas])].sort(),readSchemas:[...new Set([schema,...inspected.readSchemas])].sort(),opaque:inspected.opaque,behavior:{schema,verb,from,to,dependencies:inspected.dependencies}}),generated.push(d)}for(let tool of mediaDefinitions()){addRoute(`admin:tool:${tool.name}`,[],tool.inputSchema);let d=out.at(-1);Object.assign(d,{name:tool.name,readOnly:tool.annotations.readOnlyHint,opaque:!tool.annotations.readOnlyHint,annotations:tool.annotations,behavior:{tool,schemas:plan.schemas}}),generated.push(d)}for(let route of adminRoutes){let deps=route.includes("/views/:")||route==="GET /view-batch"?declarations.filter(d2=>d2.kind==="view"&&(route.includes("/developer/")||plan.views[d2.id.slice(5)]?.surface==="admin")):route.includes("/operations/:")?declarations.filter(d2=>adminOperations.has(d2.id)):route==="POST /webmcp/:tool"?declarations.filter(d2=>mcpTools(plan,"admin").some(t=>`${t.kind}:${t.source}`===d2.id)).concat(generated):[],generic=route.includes("/entries")||route.includes("/statistics")||route==="GET /bootstrap";addRoute(`admin:${route}`,deps,deps.length&&route!=="GET /view-batch"?{...objectInput,anyOf:deps.map(d2=>({type:"object",properties:{[route.includes("/webmcp/")?"tool":"name"]:{type:"string",const:route.includes("/webmcp/")?d2.name:d2.id.slice(d2.kind==="view"?5:10)}}}))}:objectInput);let d=out.at(-1);if(generic){let operation=route==="POST /entries"?"create":route==="DELETE /entries/:id"?"delete":route.startsWith("GET ")?void 0:route.endsWith("/publish")?"publish":"update",inspected=inspectBehavior(plan,void 0,!1,void 0,void 0,operation?Object.keys(plan.schemas).map(schema=>({schema,operation})):[]);d.schemas=Object.keys(plan.schemas).sort(),d.readSchemas=d.schemas,d.opaque=inspected.opaque,d.behavior={schemas:plan.schemas,dependencies:inspected.dependencies}}}return[...new Map(out.map(d=>[d.id,d])).values()].sort((a,b)=>a.id<b.id?-1:a.id>b.id?1:0)}async function hash(value){let digest=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(canonical(value)));return[...new Uint8Array(digest)].map(b=>b.toString(16).padStart(2,"0")).join("")}function hashDefinition(plan,d){return hash({id:d.id,surfaces:d.surfaces,triggers:d.triggers,behavior:d.behavior,input:d.inputSchema,requires:d.requires,restrict:d.restrict,dialect:plan.dialect,schemas:Object.fromEntries(d.schemas.map(name=>[name,plan.schemas[name]])),userFields:plan.access?.userFields,readOnly:d.readOnly,opaque:d.opaque,trustedOnly:d.trustedOnly})}async function targetHash(plan,targetId){let target=targetDefinitions(plan).find(d=>d.id===targetId);if(!target)throw new TypeError(`Unknown target '${targetId}'.`);return hashDefinition(plan,target)}export{canonical,targetDefinitions,hash,hashDefinition,targetHash};
|