@ductape/mcp 0.2.31 → 0.2.32

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 (2) hide show
  1. package/dist/index.js +104 -13
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -945,6 +945,9 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
945
945
  // ctx.input – typed runtime input; always compiles to $Input{} operators
946
946
  // ctx.sampleInput – compile-time sample for loop/branch discovery only
947
947
  // ctx.step(tag, fn, rollback?, opts?) – define a durable step
948
+ // ctx.when.eq/ne/gt/gte/lt/lte/truthy/falsy(...) – build portable runtime conditions
949
+ // ctx.branch(condition, { then, else? }) – record both paths as conditioned steps
950
+ // ctx.each(ctx.sampleInput.items, async (item, index) => ...) – deterministic compile-time expansion
948
951
  // ctx.api.run({ app, action, input }) – call an app action (NOT 'event' -- that field name
949
952
  // only applies to ctx.database/ctx.notification/ctx.storage below, never ctx.api/ctx.action)
950
953
  // ctx.database.execute({ database, action, input }) for a saved database action
@@ -3373,6 +3376,20 @@ CONFIGURATION BOUNDARY
3373
3376
  Workbench is also supported. Never route their administrative create/update methods through
3374
3377
  ductape_execute: its publishable-key runtime proxy will fail.
3375
3378
 
3379
+ CLI COMMANDS (aliases are normalized; use these exact shapes):
3380
+ ductape resources feature list --product <product> --json
3381
+ ductape resources feature get -t <feature> --product <product> --json
3382
+ ductape resources quota list --product <product> --json
3383
+ ductape resources quota get -t <quota> --product <product> --json
3384
+ ductape resources fallback list --product <product> --json
3385
+ ductape resources fallback get -t <fallback> --product <product> --json
3386
+ ductape resources health list --product <product> --json
3387
+ ductape resources health get -t <healthcheck> --product <product> --json
3388
+ Create/update use the same resource type plus -f <asset.json>. Singular/plural feature(s),
3389
+ fallback(s), and healthcheck(s) aliases are accepted, but canonical generated commands should
3390
+ use feature, quota, fallback, and health. These commands are administrative catalogue CRUD;
3391
+ runtime run/execute operations belong to the SDK or authenticated execution surface.
3392
+
3376
3393
  QUOTAS — weighted/provider-capacity routing pools (NOT request rate limiting):
3377
3394
  Workbench definition shape:
3378
3395
  {
@@ -3929,15 +3946,83 @@ STEP 6 — WRITE the feature into the project codebase
3929
3946
  script does, invoked only through the explicit CLI command.
3930
3947
 
3931
3948
  STEP 7 — HANDLE conditionals, loops, and branching (when applicable)
3932
- Branch on step result (early return in handler):
3933
- Add branchOverrides: { stepTag: { field: value } } so all branches are recorded
3934
- At runtime the executor evaluates the real result and skips or runs later steps accordingly
3949
+ MANDATORY CONTROL-FLOW RULES:
3950
+ 1. A stored Feature executes its compiled steps. The JavaScript handler is NOT rerun at runtime.
3951
+ 2. Never use if/else, switch, ?:, ??, &&, ||, optional chaining, map, filter, find, some,
3952
+ every, reduce, for, for..of, forEach, or while on a ctx.step() result or ctx.input proxy.
3953
+ JavaScript would evaluate the recording proxy, not the future runtime value.
3954
+ 3. Use ctx.branch(ctx.when.*, { then, else }) for runtime decisions.
3955
+ 4. Use ctx.each only with a concrete ctx.sampleInput array to expand a fixed step graph.
3956
+ 5. Put runtime-sized collection algorithms and arbitrary business logic in a registered
3957
+ portable Function, then invoke it through ctx.functions inside a step.
3958
+ 6. Direct Date.now() and Math.random() are forbidden in handlers. Use ctx.transform.now(),
3959
+ ctx.transform.uuid(), and ctx.transform.concat/replace/substring/upper/lower/trim so values
3960
+ are generated at execution time. Use a portable Function for domain-specific generators.
3961
+ 7. Never use branchOverrides in newly generated code. It exists only to migrate old handlers.
3962
+
3963
+ Prefer explicit portable branching for step results:
3964
+ const result = await ctx.step("find", () => ctx.database.execute(...));
3965
+ await ctx.branch(ctx.when.eq(result.count, 0), {
3966
+ then: () => ctx.step("create", () => ctx.database.execute(...)),
3967
+ else: () => ctx.step("reuse", () => ctx.database.execute(...)),
3968
+ });
3969
+ → ctx.when also supports ne, gt, gte, lt, lte, truthy, falsy, and/or.
3970
+ → Do not use ordinary if/else, ternary, or ?? directly on an unresolved step result.
3971
+ → branchOverrides remains a compatibility escape hatch for older Feature source.
3972
+ → If both paths must feed a later common step, do not assume ctx.branch returns a selected
3973
+ value. Either record the downstream operation inside each path, persist a shared result that
3974
+ the later step can read, or move the find-or-create algorithm into one portable Function.
3935
3975
  Loop over input array:
3936
- → Add recordInput: { items: [{ id: "1" }, { id: "2" }] } and iterate ctx.sampleInput.items
3976
+ → Add recordInput: { items: [{ id: "1" }, { id: "2" }] } and call
3977
+ ctx.each(ctx.sampleInput.items, async (item, index) => { ... })
3937
3978
  → Use unique step tags per iteration (e.g. "process-" + item.id)
3979
+ → map/filter/find/indexing on ctx.sampleInput are compile-time graph discovery only.
3980
+ → Runtime-sized map/filter/find/some/every and arbitrary loops belong in a registered portable
3981
+ Function invoked through ctx.functions; never guess or freeze their results during recording.
3938
3982
  Switch/if-else on feature input values:
3939
- Add recordScenarios: [{ type: "a" }, { type: "b" }] — handler runs once per scenario
3940
- Only the scenario whose input matches runs at execution time
3983
+ Prefer ctx.branch(ctx.when.eq(ctx.input.type, "a"), { then, else }).
3984
+ recordScenarios is for legacy compile-time graph discovery, not a runtime branch primitive.
3985
+
3986
+ SAFE COPY-READY PATTERNS:
3987
+ Runtime input branch:
3988
+ await ctx.branch(ctx.when.eq(ctx.input.kind, "refund"), {
3989
+ then: () => ctx.step("refund", () => ctx.api.run({ app, action: "refund", input: {...} })),
3990
+ else: () => ctx.step("charge", () => ctx.api.run({ app, action: "charge", input: {...} })),
3991
+ });
3992
+ Nested/indexed result check:
3993
+ await ctx.branch(ctx.when.eq(search.data.length, 0), { then: ..., else: ... });
3994
+ Compound condition:
3995
+ await ctx.branch(ctx.when.and(
3996
+ ctx.when.eq(result.status, "ready"),
3997
+ ctx.when.gt(result.count, 0),
3998
+ ), { then: ..., else: ... });
3999
+ Runtime payment/reference string (never frozen at sync time):
4000
+ const reference = ctx.transform.concat(
4001
+ "pay_", ctx.transform.now(), "_", ctx.transform.uuid(),
4002
+ );
4003
+ await ctx.step("charge", () => ctx.fallback.execute({
4004
+ fallback: "charge-payment", input: { reference, ... },
4005
+ }));
4006
+ Fixed graph expansion:
4007
+ recordInput: { regions: ["ng", "gh"] },
4008
+ handler: async (ctx) => ctx.each(ctx.sampleInput.regions, async (region, index) => {
4009
+ await ctx.step("sync-region-" + index, () => ctx.functions.invoke(syncFn, "run", { region }));
4010
+ });
4011
+
4012
+ UNSAFE — NEVER GENERATE:
4013
+ if (result.count === 0) await ctx.step(...);
4014
+ const customer = found[0] ?? await ctx.step(...);
4015
+ ctx.input.items.map(item => ctx.step(...));
4016
+ for (const item of ctx.input.items) await ctx.step(...);
4017
+ const reference = "pay_" + Date.now() + "_" + Math.random();
4018
+
4019
+ BEFORE SYNCING A FEATURE:
4020
+ → Confirm every runtime decision uses ctx.branch + ctx.when.
4021
+ → Confirm every ctx.step tag is unique across both paths and every expanded iteration.
4022
+ → Confirm no runtime proxy is consumed by native JavaScript control flow or array iteration.
4023
+ → Confirm every ctx.step contains a portable ctx component/function call.
4024
+ → Compile locally, inspect schema.steps, and verify both branch paths, condition, depends_on,
4025
+ and operator-valued inputs are present before persisting.
3941
4026
 
3942
4027
  STEP 8 — SET rollbacks for reversible steps
3943
4028
  Any step that allocates a resource should undo it if a later step fails.
@@ -4047,16 +4132,18 @@ Feature statuses: pending | running | completed | failed | rolled_back | rolling
4047
4132
 
4048
4133
  ━━━ FEATURE RECORDING SEMANTICS ━━━
4049
4134
 
4050
- When you call features.define({ handler }), the handler runs TWICE:
4135
+ When you call features.define({ handler }), there are two distinct phases, but the handler itself
4136
+ runs only during compilation/recording:
4051
4137
 
4052
4138
  1. RECORDING PHASE (at define time) — handler is called with a RecordingContext.
4053
4139
  All ctx.step() calls return lightweight proxy objects, not real data.
4054
4140
  This phase captures the step graph: which steps exist, their types, tags, and declared
4055
4141
  inputs/outputs. No real API calls, DB queries, or side effects occur.
4056
4142
  Arbitrary JS code OUTSIDE ctx.step() ALSO runs during recording — with proxy values.
4057
- For loops: supply recordInput and iterate ctx.sampleInput so all iterations are recorded.
4143
+ For loops: supply recordInput and use ctx.each(ctx.sampleInput.items, ...) so all iterations are recorded.
4058
4144
  ctx.input is always the runtime operator surface and must never expose recordInput literals.
4059
- For branches: use branchOverrides so each path is captured.
4145
+ For branches: use ctx.branch(ctx.when.*, { then, else }) so both paths are captured. Use
4146
+ branchOverrides only for compatibility with existing Feature handlers.
4060
4147
  Never make an authorization, validation, tenancy, or other security decision by branching on
4061
4148
  ctx.input during recording. Use ctx.sampleInput only to discover graph shape; enforce security
4062
4149
  invariants inside a runtime portable Function or recorded step.
@@ -4066,11 +4153,15 @@ When you call features.define({ handler }), the handler runs TWICE:
4066
4153
  integrations and project readiness hooks must not start HTTP listeners, provider probes,
4067
4154
  schedulers, or Event consumers. Apply the filter before booting unrelated service modules.
4068
4155
 
4069
- 2. EXECUTION PHASE (at runtime) — handler is called with a real ExecutionContext.
4070
- ctx.step() actually executes. All real Ductape component calls happen.
4071
- Arbitrary JS logic (math, string ops, conditionals on step results) runs for real.
4156
+ 2. EXECUTION PHASE (at runtime) — the stored schema.steps graph is interpreted by FeatureExecutor.
4157
+ The original JavaScript handler is NOT called. Step inputs and conditions resolve operators
4158
+ such as $Input{}, $Sequence{}, $Step{}, and $Now against runtime state. Only recorded portable
4159
+ component/function operations execute.
4072
4160
 
4073
- Implication: put all meaningful business logic INSIDE ctx.step() handlers, not in the
4161
+ Implication: arbitrary JavaScript control flow never becomes runtime behavior merely because it
4162
+ appears in the handler. Express runtime branches through ctx.branch/ctx.when, fixed graph expansion
4163
+ through ctx.each(ctx.sampleInput...), and runtime-sized algorithms through ctx.functions. Put all
4164
+ meaningful business logic INSIDE recorded portable operations, not in the
4074
4165
  outer handler body. Code in the outer body runs during recording with proxy values and
4075
4166
  may behave unexpectedly (e.g. typeof proxy === 'object' is true but .someField is a proxy).
4076
4167
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.2.31",
3
+ "version": "0.2.32",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "build": "tsc",
18
- "test": "npm run build && node scripts/check-cli-command-security.mjs && node scripts/check-frontend-analytics-guidance.mjs && node scripts/check-events-discovery.mjs && node scripts/check-schema-fallback.mjs && node scripts/check-paystack-action-schema.mjs && node scripts/check-portable-functions.mjs && node scripts/check-project-link-guidance.mjs && node scripts/check-graph-vector-projection-guidance.mjs && node scripts/check-asset-file-guidance.mjs",
18
+ "test": "npm run build && node scripts/check-cli-command-security.mjs && node scripts/check-frontend-analytics-guidance.mjs && node scripts/check-events-discovery.mjs && node scripts/check-schema-fallback.mjs && node scripts/check-paystack-action-schema.mjs && node scripts/check-portable-functions.mjs && node scripts/check-feature-control-flow.mjs && node scripts/check-project-link-guidance.mjs && node scripts/check-graph-vector-projection-guidance.mjs && node scripts/check-asset-file-guidance.mjs",
19
19
  "start": "node dist/index.js",
20
20
  "dev": "tsx src/index.ts"
21
21
  },