@jaypie/mcp 0.8.157 → 0.8.159
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 +11 -0
- package/dist/assets.d.ts +25 -2
- package/dist/assets.js +27 -6
- package/dist/assets.js.map +1 -1
- package/dist/suites/docs/help.d.ts +1 -1
- package/dist/suites/docs/help.js +17 -0
- package/dist/suites/docs/help.js.map +1 -1
- package/dist/suites/docs/index.d.ts +2 -0
- package/dist/suites/docs/index.js +144 -28
- package/dist/suites/docs/index.js.map +1 -1
- package/package.json +1 -1
- package/release-notes/constructs/1.2.86.md +42 -0
- package/release-notes/constructs/1.2.87.md +19 -0
- package/release-notes/mcp/0.8.158.md +19 -0
- package/release-notes/mcp/0.8.159.md +50 -0
- package/release-notes/testkit/1.2.74.md +12 -0
- package/release-notes/tildeskill/0.3.5.md +34 -0
- package/skills/cdk.md +8 -4
- package/skills/cicd-actions.md +7 -9
- package/skills/cicd-deploy.md +18 -15
- package/skills/cicd-environments.md +5 -1
- package/skills/debugging.md +6 -2
- package/skills/dynamodb.md +58 -6
- package/skills/github.md +1 -1
- package/skills/jaypie.md +1 -1
- package/skills/mcp.md +16 -1
- package/skills/migrations.md +21 -9
- package/skills/recipe-api-server.md +2 -1
- package/skills/releasenotes.md +7 -4
- package/skills/secrets.md +63 -1
- package/skills/tildeskill.md +77 -2
- package/skills/variables.md +3 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 0.8.159
|
|
3
|
+
date: 2026-09-14
|
|
4
|
+
summary: PROJECT_NONCE is a required 8-hex environment variable in every skill
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- **`cicd-actions`** makes `project-nonce` `required: true` in
|
|
10
|
+
`setup-environment` and fails with `::error::` when it is empty. The branch
|
|
11
|
+
name and `prod` fallbacks are removed (#554).
|
|
12
|
+
- **`cicd-environments`** moves `PROJECT_NONCE` to Required Variables (8
|
|
13
|
+
lowercase hex, unique per environment, `openssl rand -hex 4`) and adds it to
|
|
14
|
+
the sandbox and production examples.
|
|
15
|
+
- **`cicd-deploy`** passes `project-nonce: ${{ vars.PROJECT_NONCE }}` in both
|
|
16
|
+
deploy jobs, replaces hand-rolled `api-${env}-${nonce}` naming with the
|
|
17
|
+
`JaypieStack` generated name, and adds One Install per Environment.
|
|
18
|
+
- **`variables`**, **`cdk`**, **`jaypie`**, **`recipe-api-server`**, and
|
|
19
|
+
**`debugging`** use hex nonces in place of `dev`, `staging`, and `prod`.
|
|
20
|
+
- **`cdk`** documents the `@jaypie/constructs:projectNonceFormat` synth
|
|
21
|
+
warning.
|
|
22
|
+
- **`github`** notes that `setup-environment` fails on an empty
|
|
23
|
+
`project-nonce` instead of falling back.
|
|
24
|
+
- **`@jaypie/mcp/assets`** exports `getMcpBuildInfo()`, which returns
|
|
25
|
+
`{ commit, version, versionString }` from the same build-time replacements the
|
|
26
|
+
`version` tool reports. A consumer syncing `skills/` and `release-notes/`
|
|
27
|
+
reads the build from the same package. The **`mcp`** skill documents it
|
|
28
|
+
(#555).
|
|
29
|
+
- **`tildeskill`** documents that layered `find` checks every layer for an
|
|
30
|
+
exact alias before plural/singular fallback (#558).
|
|
31
|
+
- **`tildeskill`** documents `createDynamoDbStore` from
|
|
32
|
+
`@jaypie/tildeskill/dynamodb`, `syncSkills`, and `delete` semantics for every
|
|
33
|
+
store. **`dynamodb`** notes that skills use `indexModelCategory` and that
|
|
34
|
+
`registerSkillModel()` declares it (#557).
|
|
35
|
+
- **`tildeskill`** documents that `createSkillService` throws `BadRequestError`
|
|
36
|
+
for an invalid alias and `NotFoundError` for a missing skill.
|
|
37
|
+
- **`release_notes("list")`** paginates. `limit` defaults to 50 and caps at
|
|
38
|
+
200. Notes sort by package ascending, then version descending. A
|
|
39
|
+
`Next page: release_notes("list", { cursor: "..." })` footer appears only
|
|
40
|
+
when more notes exist, and its opaque cursor carries `package`,
|
|
41
|
+
`since_version`, and `limit`. Invalid cursors and filters that conflict with
|
|
42
|
+
the cursor return a bad request. The unfiltered first page drops from about
|
|
43
|
+
61K to about 5.7K characters. The **`mcp`** and **`releasenotes`** skills
|
|
44
|
+
document it (#556).
|
|
45
|
+
|
|
46
|
+
## Upgrade
|
|
47
|
+
|
|
48
|
+
- Projects built from the previous `cicd-actions` template may have deployed
|
|
49
|
+
branch-name nonces. Set `PROJECT_NONCE` on each GitHub environment. The new
|
|
50
|
+
value creates a new install, so tear down the old stacks and buckets.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 1.2.74
|
|
3
|
+
date: 2026-09-14
|
|
4
|
+
summary: Tildeskill mocks cover the DynamoDB store, syncSkills, and SkillStore delete
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- Mock stores implement `delete`, resolving `false` (#557).
|
|
10
|
+
- New tildeskill mocks: `createDynamoDbStore`, `hashSkill`,
|
|
11
|
+
`registerSkillModel`, `syncSkills` (resolves empty lists), and the
|
|
12
|
+
`SKILL_MODEL`, `SKILL_MODEL_NAME`, and `SKILL_NAMESPACE` constants (#557).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 0.3.5
|
|
3
|
+
date: 2026-09-14
|
|
4
|
+
summary: DynamoDB skill store, syncSkills, and SkillStore delete; layered find prefers an exact alias in any layer
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- `createLayeredStore().find` checks every layer for an exact alias before it
|
|
10
|
+
tries plural/singular fallback in any layer. With `studio:test` and
|
|
11
|
+
`jaypie:tests`, `find("tests")` returns `jaypie:tests` in place of
|
|
12
|
+
`studio:test`, so a local skill cannot shadow an existing Jaypie skill under
|
|
13
|
+
a different spelling (#558).
|
|
14
|
+
- `createSkillService` resolves through `find`, so `skill("tests")` follows the
|
|
15
|
+
same order. Namespace-qualified aliases still search only their own layer.
|
|
16
|
+
- `createDynamoDbStore({ category })` from the new `@jaypie/tildeskill/dynamodb`
|
|
17
|
+
subpath serves skills from `@jaypie/dynamodb` as `skill` entities with
|
|
18
|
+
deterministic ids (`uuidv5("<category>:<alias>", SKILL_NAMESPACE)`) and
|
|
19
|
+
`metadata.hash`. `@jaypie/dynamodb` is an optional peer dependency that the
|
|
20
|
+
main entry never imports. The store loads it with a dynamic import on first
|
|
21
|
+
use, so a CJS consumer shares the host's initialized client (#557).
|
|
22
|
+
- `syncSkills({ from, to })` puts added and changed records, leaves unchanged
|
|
23
|
+
records alone, deletes records missing from `from`, and returns
|
|
24
|
+
`{ added, removed, unchanged, updated }`. An empty `from` with a non-empty
|
|
25
|
+
`to` throws `ConfigurationError` unless `allowEmptySource: true` (#557).
|
|
26
|
+
- `SkillStore` gains `delete(alias)`. Memory removes the record, markdown
|
|
27
|
+
removes the file, layered requires a namespace-qualified alias, and DynamoDB
|
|
28
|
+
soft deletes (#557).
|
|
29
|
+
- New main exports: `hashSkill`, `registerSkillModel`, `SKILL_MODEL`,
|
|
30
|
+
`SKILL_MODEL_NAME`, `syncSkills`, and the `DynamoDbStoreOptions`,
|
|
31
|
+
`SyncSkillsOptions`, and `SyncSkillsResult` types.
|
|
32
|
+
- `createSkillService` throws `BadRequestError` for an invalid alias and
|
|
33
|
+
`NotFoundError` for a missing skill in place of a vanilla `Error`. Messages
|
|
34
|
+
are unchanged.
|
package/skills/cdk.md
CHANGED
|
@@ -131,7 +131,7 @@ workspaces/
|
|
|
131
131
|
`cdk-{PROJECT_SPONSOR}-{PROJECT_KEY}-{PROJECT_ENV}-{PROJECT_NONCE}[-{key}]`,
|
|
132
132
|
so naming stays consistent across sandbox, personal, and production deploys.
|
|
133
133
|
2. **Account & region** resolved from `CDK_DEFAULT_ACCOUNT` / `CDK_DEFAULT_REGION` (overridable via `env`).
|
|
134
|
-
3. **Standard tags** applied to the stack and propagated to every taggable child resource: `env`, `project`, `sponsor`, `nonce`, `commit`, `buildHex`, `buildDate`, `buildTime`, `version`, `service`, `creation`, `role`, `stack`. Per-build tags
|
|
134
|
+
3. **Standard tags** applied to the stack and propagated to every taggable child resource: `env`, `project`, `sponsor`, `nonce`, `commit`, `buildHex`, `buildDate`, `buildTime`, `version`, `service`, `creation`, `role`, `stack`. Per-build tags update every table on every deploy, so a table must declare its indexes in CDK (see `skill("dynamodb")`).
|
|
135
135
|
|
|
136
136
|
```typescript
|
|
137
137
|
import { Construct } from "constructs";
|
|
@@ -153,6 +153,8 @@ Props extend `StackProps` with one addition:
|
|
|
153
153
|
|
|
154
154
|
If you see `undefined` or `unknown` in a generated stack name or tag, an env var is missing — fix the environment rather than overriding `stackName`.
|
|
155
155
|
|
|
156
|
+
`JaypieStack` warns at synth (`@jaypie/constructs:projectNonceFormat`) when `CDK_DEFAULT_ACCOUNT` is set and `PROJECT_NONCE` is unset or is not lowercase hex of six or more characters. The warning fires before the first deploy makes a word or branch-name nonce permanent.
|
|
157
|
+
|
|
156
158
|
#### `JaypieAppStack` / `JaypieInfrastructureStack`
|
|
157
159
|
|
|
158
160
|
Thin convenience subclasses that pre-fill `key`:
|
|
@@ -206,12 +208,14 @@ See `skill("variables")` for the role of `PROJECT_ENV` and `PROJECT_NONCE`.
|
|
|
206
208
|
|
|
207
209
|
```bash
|
|
208
210
|
# Deploy to sandbox
|
|
209
|
-
PROJECT_ENV=sandbox PROJECT_NONCE=
|
|
211
|
+
PROJECT_ENV=sandbox PROJECT_NONCE=3f9c21ab cdk deploy
|
|
210
212
|
|
|
211
213
|
# Deploy to production
|
|
212
|
-
PROJECT_ENV=production PROJECT_NONCE=
|
|
214
|
+
PROJECT_ENV=production PROJECT_NONCE=ba342b91 cdk deploy
|
|
213
215
|
```
|
|
214
216
|
|
|
217
|
+
Deploys run in CI (see `skill("cicd-deploy")`). Each environment carries its own `PROJECT_NONCE`, 8 lowercase hex characters generated once with `openssl rand -hex 4`. See `skill("github")` for provisioning.
|
|
218
|
+
|
|
215
219
|
## Lambda with DynamoDB Tables
|
|
216
220
|
|
|
217
221
|
Pass tables to `JaypieLambda` for automatic permissions and environment wiring:
|
|
@@ -235,7 +239,7 @@ const handler = new JaypieLambda(this, "ApiHandler", {
|
|
|
235
239
|
| 1 table | `grantReadWriteData()` plus `Query`/`Scan` on `index/*` | `DYNAMODB_TABLE_NAME` set automatically |
|
|
236
240
|
| 2+ tables | `grantReadWriteData()` plus `Query`/`Scan` on `index/*` for each | No auto env var — set `CDK_ENV_TABLE` manually |
|
|
237
241
|
|
|
238
|
-
|
|
242
|
+
The explicit `index/*` grant also covers indexes on imported tables, which CDK does not know about.
|
|
239
243
|
|
|
240
244
|
Single table example — at runtime, use `process.env.DYNAMODB_TABLE_NAME`:
|
|
241
245
|
|
package/skills/cicd-actions.md
CHANGED
|
@@ -49,9 +49,8 @@ inputs:
|
|
|
49
49
|
description: 'Sponsor segment of the generated stack name'
|
|
50
50
|
required: true
|
|
51
51
|
project-nonce:
|
|
52
|
-
description: '
|
|
53
|
-
required:
|
|
54
|
-
default: ''
|
|
52
|
+
description: '8 lowercase hex characters, unique per environment'
|
|
53
|
+
required: true
|
|
55
54
|
log-level:
|
|
56
55
|
description: 'Log level (trace, debug, info, warn, error)'
|
|
57
56
|
required: false
|
|
@@ -92,14 +91,11 @@ runs:
|
|
|
92
91
|
echo "PROJECT_KEY=${{ inputs.project-key }}" >> $GITHUB_ENV
|
|
93
92
|
echo "PROJECT_SPONSOR=${{ inputs.project-sponsor }}" >> $GITHUB_ENV
|
|
94
93
|
|
|
95
|
-
# Resolve PROJECT_NONCE (
|
|
94
|
+
# Resolve PROJECT_NONCE (required, no fallback)
|
|
96
95
|
NONCE="${{ inputs.project-nonce }}"
|
|
97
96
|
if [ -z "$NONCE" ]; then
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
else
|
|
101
|
-
NONCE="${GITHUB_REF_NAME//\//-}"
|
|
102
|
-
fi
|
|
97
|
+
echo "::error::project-nonce is empty. Set PROJECT_NONCE on the GitHub environment (openssl rand -hex 4)"
|
|
98
|
+
exit 1
|
|
103
99
|
fi
|
|
104
100
|
echo "project-nonce=${NONCE}" >> $GITHUB_OUTPUT
|
|
105
101
|
echo "PROJECT_NONCE=${NONCE}" >> $GITHUB_ENV
|
|
@@ -131,6 +127,8 @@ runs:
|
|
|
131
127
|
|
|
132
128
|
`project-sponsor` is `required: true` on purpose. `PROJECT_SPONSOR` is the first segment of the generated stack name (`cdk-{PROJECT_SPONSOR}-{PROJECT_KEY}-{PROJECT_ENV}-{PROJECT_NONCE}`), and an optional input with an empty default deploys `cdk-undefined-...` silently. A stack name is immutable, so correcting it later means a stack replacement. See `skill("cdk")` for stack naming and `skill("variables")` for the full variable reference.
|
|
133
129
|
|
|
130
|
+
`project-nonce` is `required: true` with no fallback, and the step exits with `::error::` when it is empty because GitHub does not enforce `required` on composite action inputs. `PROJECT_NONCE` is 8 lowercase hex characters, generated once per environment (`openssl rand -hex 4`) and stored as a GitHub environment variable (see `skill("cicd-environments")`). It lands in globally unique names (S3 buckets), SSM parameter paths, and immutable stack names. A branch name or word such as `prod` repeats across projects and accounts, and changing the nonce later means a new install beside the old one.
|
|
131
|
+
|
|
134
132
|
## configure-aws/action.yml
|
|
135
133
|
|
|
136
134
|
Configures AWS credentials via OIDC.
|
package/skills/cicd-deploy.md
CHANGED
|
@@ -75,6 +75,7 @@ jobs:
|
|
|
75
75
|
project-key: my-project
|
|
76
76
|
project-sponsor: ${{ vars.PROJECT_SPONSOR }}
|
|
77
77
|
project-env: sandbox
|
|
78
|
+
project-nonce: ${{ vars.PROJECT_NONCE }}
|
|
78
79
|
|
|
79
80
|
- uses: ./.github/actions/configure-aws
|
|
80
81
|
with:
|
|
@@ -163,6 +164,7 @@ jobs:
|
|
|
163
164
|
project-key: my-project
|
|
164
165
|
project-sponsor: ${{ vars.PROJECT_SPONSOR }}
|
|
165
166
|
project-env: production
|
|
167
|
+
project-nonce: ${{ vars.PROJECT_NONCE }}
|
|
166
168
|
|
|
167
169
|
- uses: ./.github/actions/configure-aws
|
|
168
170
|
with:
|
|
@@ -271,27 +273,28 @@ Requirements:
|
|
|
271
273
|
|
|
272
274
|
See `skill("cicd-actions")` for the `web-deploy` action body and `skill("web")` for the construct.
|
|
273
275
|
|
|
274
|
-
##
|
|
276
|
+
## Stack Naming
|
|
275
277
|
|
|
276
|
-
|
|
278
|
+
`JaypieStack` names every stack `cdk-{PROJECT_SPONSOR}-{PROJECT_KEY}-{PROJECT_ENV}-{PROJECT_NONCE}[-{key}]` and resolves the account and region (see `skill("cdk")`). Do not compose stack names in `app.ts`:
|
|
277
279
|
|
|
278
280
|
```typescript
|
|
279
281
|
// workspaces/cdk/src/app.ts
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
account: process.env.CDK_DEFAULT_ACCOUNT,
|
|
286
|
-
region: process.env.CDK_DEFAULT_REGION,
|
|
287
|
-
},
|
|
288
|
-
});
|
|
282
|
+
import { App } from "aws-cdk-lib";
|
|
283
|
+
import { JaypieAppStack } from "@jaypie/constructs";
|
|
284
|
+
|
|
285
|
+
const app = new App();
|
|
286
|
+
new JaypieAppStack(app, "AppStack");
|
|
289
287
|
```
|
|
290
288
|
|
|
291
|
-
|
|
292
|
-
- `
|
|
293
|
-
- `
|
|
294
|
-
|
|
289
|
+
With `PROJECT_NONCE` set per environment (see `skill("cicd-environments")`), this produces:
|
|
290
|
+
- `cdk-finlaysonstudio-my-project-sandbox-3f9c21ab-app`
|
|
291
|
+
- `cdk-finlaysonstudio-my-project-production-ba342b91-app`
|
|
292
|
+
|
|
293
|
+
The `cdk-deploy` globs `*-sandbox-*` and `*-production-*` match these names.
|
|
294
|
+
|
|
295
|
+
### One Install per Environment
|
|
296
|
+
|
|
297
|
+
The nonce belongs to the environment, so every branch that deploys to sandbox updates the same stacks. A project serving fixed hostnames (`api.sandbox.example.com`) holds one install per environment, because CloudFront aliases and Route53 records collide. Override `project-nonce` with a new hex value only for an intentional clean install beside an existing one. Stack and bucket names cannot be renamed, so recovering from a wrong nonce means that new install and a teardown of the old one.
|
|
295
298
|
|
|
296
299
|
## Deployment Flow
|
|
297
300
|
|
|
@@ -12,6 +12,9 @@ GitHub Environments provide deployment-specific variables for Jaypie CDK workflo
|
|
|
12
12
|
| Variable | Description |
|
|
13
13
|
|----------|-------------|
|
|
14
14
|
| `AWS_ROLE_ARN` | OIDC role ARN for assuming AWS credentials |
|
|
15
|
+
| `PROJECT_NONCE` | 8 lowercase hex characters, unique per environment |
|
|
16
|
+
|
|
17
|
+
Generate `PROJECT_NONCE` once per environment with `openssl rand -hex 4`. It scopes stack names, S3 bucket names, and SSM paths, so a new value deploys a new install and strands the old one. `setup-environment` fails when it is missing (see `skill("cicd-actions")`).
|
|
15
18
|
|
|
16
19
|
## Optional Variables
|
|
17
20
|
|
|
@@ -21,7 +24,6 @@ GitHub Environments provide deployment-specific variables for Jaypie CDK workflo
|
|
|
21
24
|
| `LOG_LEVEL` | `trace` (sandbox) / `info` (production) | Application log level |
|
|
22
25
|
| `PROJECT_CHAOS` | `full` (sandbox) / `none` (production) | Chaos engineering mode |
|
|
23
26
|
| `PROJECT_ENV` | Environment name | Environment identifier |
|
|
24
|
-
| `PROJECT_NONCE` | Branch name or `prod` | Unique identifier for resources |
|
|
25
27
|
|
|
26
28
|
## Environment Setup
|
|
27
29
|
|
|
@@ -43,6 +45,7 @@ GitHub Environments provide deployment-specific variables for Jaypie CDK workflo
|
|
|
43
45
|
AWS_ROLE_ARN = arn:aws:iam::123456789012:role/GitHubActions-Sandbox
|
|
44
46
|
AWS_REGION = us-east-1
|
|
45
47
|
PROJECT_ENV = sandbox
|
|
48
|
+
PROJECT_NONCE = 3f9c21ab
|
|
46
49
|
LOG_LEVEL = trace
|
|
47
50
|
PROJECT_CHAOS = full
|
|
48
51
|
```
|
|
@@ -53,6 +56,7 @@ PROJECT_CHAOS = full
|
|
|
53
56
|
AWS_ROLE_ARN = arn:aws:iam::123456789012:role/GitHubActions-Production
|
|
54
57
|
AWS_REGION = us-east-1
|
|
55
58
|
PROJECT_ENV = production
|
|
59
|
+
PROJECT_NONCE = ba342b91
|
|
56
60
|
LOG_LEVEL = info
|
|
57
61
|
PROJECT_CHAOS = none
|
|
58
62
|
```
|
package/skills/debugging.md
CHANGED
|
@@ -39,11 +39,15 @@ If you see `undefined` in a CDK stack name, a required variable is missing:
|
|
|
39
39
|
// BAD: undefined if PROJECT_NONCE not set
|
|
40
40
|
const stackName = `api-${process.env.PROJECT_NONCE}`;
|
|
41
41
|
|
|
42
|
-
//
|
|
42
|
+
// BAD: a word default repeats across projects and accounts
|
|
43
43
|
const nonce = process.env.PROJECT_NONCE || "dev";
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
// GOOD: JaypieStack names the stack from PROJECT_* variables
|
|
46
|
+
new JaypieAppStack(app, "AppStack");
|
|
45
47
|
```
|
|
46
48
|
|
|
49
|
+
Set the missing variable on the GitHub environment rather than adding a default (see `skill("cicd-environments")`).
|
|
50
|
+
|
|
47
51
|
### "unknown" in Logs
|
|
48
52
|
|
|
49
53
|
`unknown` indicates a value was expected but fell back to a default:
|
package/skills/dynamodb.md
CHANGED
|
@@ -270,9 +270,9 @@ const { items: chats } = await queryByScope({ model: "chat", scope: APEX });
|
|
|
270
270
|
|
|
271
271
|
GSIs are defined using `fabricIndex()` from `@jaypie/fabric`. **Do not create all GSIs upfront** — start with zero and add only what your access patterns require. The most common first GSI is `indexModel` for listing entities by model.
|
|
272
272
|
|
|
273
|
-
**
|
|
273
|
+
**Indexes belong in CDK.** Declare every registered `fabricIndex()` in `JaypieDynamoDb` `indexes` (see [Share Indexes Between CDK and Runtime](#share-indexes-between-cdk-and-runtime)). A new table is created with all declared GSIs in one deploy. DynamoDB adds only **one GSI per update** to an existing table, so add indexes to a deployed table one per deploy.
|
|
274
274
|
|
|
275
|
-
**Grants:**
|
|
275
|
+
**Grants:** `JaypieLambda` and `JaypieMigration` grant `Query` and `Scan` on `${tableArn}/index/*` for every table in `tables`, which also covers indexes on imported tables. Hand-rolled roles must add it.
|
|
276
276
|
|
|
277
277
|
All GSIs use a composite sort key of `scope#updatedAt` (stored as `{indexName}Sk`). Queries use `begins_with` on the sk to filter by scope; omitting scope lists across all scopes.
|
|
278
278
|
|
|
@@ -366,6 +366,10 @@ const nextPage = await queryByScope({
|
|
|
366
366
|
|
|
367
367
|
`storeExchange(envelope, { exchange?, scope? })` persists an `@jaypie/llm` exchange envelope (see `skill("llm")`) as an `exchange` entity. It registers the canonical exchange model (`registerExchangeModel()` from `@jaypie/fabric`), maps envelope fields onto reserved attributes (`input`, `content`, `data`, `llm`, `status`, `xid`), and owns 400KB item-limit safety (drops the history delta, then truncates long fields, marking `metadata.truncated`). It warns and returns `null` — never throws — when `initClient()` has not run or the write fails. `@jaypie/llm` calls it automatically when `LLM_EXCHANGE_ENABLED` is set; pass `exchange` (parent id) for turn chains and `scope` to nest under a parent entity.
|
|
368
368
|
|
|
369
|
+
### Skill Storage
|
|
370
|
+
|
|
371
|
+
`@jaypie/tildeskill/dynamodb` stores skills as `skill` entities queried through `indexModelCategory` (see `skill("tildeskill")`). Call `registerSkillModel()` from `@jaypie/tildeskill` before `getAllRegisteredIndexes()` so CDK declares the index.
|
|
372
|
+
|
|
369
373
|
### TTL (Time to Live)
|
|
370
374
|
|
|
371
375
|
DynamoDB expires items whose TTL attribute holds a past Unix epoch **in seconds**. Jaypie writes that attribute (`ttl` by default) and the `JaypieDynamoDb` construct enables TTL on it by default.
|
|
@@ -474,27 +478,75 @@ const table = new JaypieDynamoDb(this, "myApp", {
|
|
|
474
478
|
|
|
475
479
|
Wire tables to Lambda using the `tables` prop — see `skill("cdk")` for details.
|
|
476
480
|
|
|
477
|
-
###
|
|
481
|
+
### Share Indexes Between CDK and Runtime
|
|
482
|
+
|
|
483
|
+
The model registry is the single source of indexes. Register models in a module that both the CDK workspace and the runtime import, then pass the registry to the construct:
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
// packages/models/src/index.ts
|
|
487
|
+
import { fabricIndex, registerModel } from "@jaypie/fabric";
|
|
488
|
+
|
|
489
|
+
registerModel({ model: "record", indexes: [fabricIndex(), fabricIndex("alias")] });
|
|
490
|
+
registerModel({ model: "session", indexes: [fabricIndex()], ttl: "30 days" });
|
|
491
|
+
```
|
|
478
492
|
|
|
479
|
-
|
|
493
|
+
```typescript
|
|
494
|
+
// workspaces/cdk/lib/data-stack.ts
|
|
495
|
+
import "@project/models"; // registers models
|
|
496
|
+
import { JaypieDynamoDb } from "@jaypie/constructs";
|
|
497
|
+
import { getAllRegisteredIndexes } from "@jaypie/fabric";
|
|
498
|
+
|
|
499
|
+
const table = new JaypieDynamoDb(this, "myApp", {
|
|
500
|
+
indexes: getAllRegisteredIndexes(),
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
`JaypieDynamoDb` sorts indexes by name, so import order does not change the template. The registry is module state: the CDK workspace and the models module must resolve the same `@jaypie/fabric` copy, or `getAllRegisteredIndexes()` returns an empty list.
|
|
505
|
+
|
|
506
|
+
### Indexes Created Outside CDK
|
|
507
|
+
|
|
508
|
+
A GSI created through `UpdateTable` (a migration, the console, or the CLI) blocks every later CloudFormation update to the table:
|
|
480
509
|
|
|
481
510
|
```
|
|
482
511
|
Invalid AttributeDefinitions Expected the following attributes to be present: [indexModel, ...]
|
|
483
512
|
```
|
|
484
513
|
|
|
485
|
-
CloudFormation sends only the
|
|
514
|
+
CloudFormation sends only the attributes the template declares, and DynamoDB rejects a request that omits attributes an existing index references. Tag, TTL, point-in-time recovery, and billing changes all fail. `JaypieMigration` grants no `UpdateTable` for this reason.
|
|
515
|
+
|
|
516
|
+
CloudFormation cannot adopt an existing GSI. Recover by declaring the indexes in `JaypieDynamoDb` and recreating the table. Outside production, delete and redeploy the stack. In production, deploy a new CDK-declared table beside the old one, copy items with `scanTable` and `updateEntity` (see [Migration: v0.4.x to v0.5.0](#migration-v04x-to-v050)), and switch `tables` to the new table.
|
|
486
517
|
|
|
487
518
|
## Local Development
|
|
488
519
|
|
|
489
520
|
Use docker-compose for local DynamoDB. The `@jaypie/dynamodb` MCP tool can generate a `docker-compose.yml` with custom ports.
|
|
490
521
|
|
|
522
|
+
`createTable()` builds the table with every registered GSI, the same indexes CDK declares. Import the models module, create the table, then run the seed the deployed migration runs (see `skill("migrations")`):
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
// scripts/dynamo-create-table.ts
|
|
526
|
+
import "@project/models"; // registers models
|
|
527
|
+
import { createTable, initClient } from "@jaypie/dynamodb";
|
|
528
|
+
|
|
529
|
+
import { seed } from "../packages/api/src/migrations/seed/seed.js";
|
|
530
|
+
|
|
531
|
+
initClient({
|
|
532
|
+
credentials: { accessKeyId: "local", secretAccessKey: "local" },
|
|
533
|
+
endpoint: "http://127.0.0.1:9060",
|
|
534
|
+
tableName: "jaypie-local",
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
await createTable();
|
|
538
|
+
await seed();
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
`createTable()` leaves an existing table unchanged and adds no indexes to it. After registering a new index, run `npm run dynamo:remove && npm run dynamo:init` to rebuild the local table.
|
|
542
|
+
|
|
491
543
|
### Suggested package.json Scripts
|
|
492
544
|
|
|
493
545
|
```json
|
|
494
546
|
{
|
|
495
547
|
"scripts": {
|
|
496
548
|
"dynamo:init": "docker compose up -d && npm run dynamo:create-table",
|
|
497
|
-
"dynamo:create-table": "
|
|
549
|
+
"dynamo:create-table": "tsx scripts/dynamo-create-table.ts",
|
|
498
550
|
"dynamo:remove": "docker compose down -v",
|
|
499
551
|
"dynamo:start": "docker compose up -d",
|
|
500
552
|
"dynamo:stop": "docker compose down"
|
package/skills/github.md
CHANGED
|
@@ -260,7 +260,7 @@ jobs:
|
|
|
260
260
|
|
|
261
261
|
- Deploy jobs declare `permissions` with `contents: read` and `id-token: write`
|
|
262
262
|
- Composite actions under `.github/actions` receive every value through `inputs` and never reference `vars.*` or `secrets.*`
|
|
263
|
-
- `setup-environment` supplies fallback defaults when an input is empty
|
|
263
|
+
- `setup-environment` supplies fallback defaults when an input is empty, except `project-nonce`, which fails the job
|
|
264
264
|
- The OIDC subject for an environment job is `repo:<org>/<repo>:environment:<env>` (see ~cicd-environments for the trust policy)
|
|
265
265
|
|
|
266
266
|
## Audit
|
package/skills/jaypie.md
CHANGED
|
@@ -66,7 +66,7 @@ project/
|
|
|
66
66
|
|----------|---------|
|
|
67
67
|
| `PROJECT_ENV` | Environment: local, sandbox, production |
|
|
68
68
|
| `PROJECT_KEY` | Project identifier for logging |
|
|
69
|
-
| `PROJECT_NONCE` |
|
|
69
|
+
| `PROJECT_NONCE` | 8 lowercase hex characters, unique per environment |
|
|
70
70
|
| `LOG_LEVEL` | Log level: trace, debug, info, warn, error |
|
|
71
71
|
|
|
72
72
|
### Error Handling
|
package/skills/mcp.md
CHANGED
|
@@ -27,11 +27,15 @@ skill("tests") # Testing patterns
|
|
|
27
27
|
|
|
28
28
|
```
|
|
29
29
|
release_notes() # Show help
|
|
30
|
-
release_notes("list") #
|
|
30
|
+
release_notes("list") # First page of all release notes
|
|
31
|
+
release_notes("list", { limit: 20 }) # Smaller page (default 50, maximum 200)
|
|
31
32
|
release_notes("list", { package: "mcp" }) # Filter by package
|
|
33
|
+
release_notes("list", { cursor: "..." }) # Next page, from the footer
|
|
32
34
|
release_notes("read", { package: "mcp", version: "0.5.0" }) # Read specific note
|
|
33
35
|
```
|
|
34
36
|
|
|
37
|
+
`list` sorts by package ascending, then version descending. When more notes exist, the last line reads `Next page: release_notes("list", { cursor: "..." })`. The cursor carries `package`, `since_version`, and `limit`, so that call alone continues the listing.
|
|
38
|
+
|
|
35
39
|
## Datadog Tool
|
|
36
40
|
|
|
37
41
|
Unified tool for logs, monitors, metrics, synthetics, and RUM.
|
|
@@ -134,6 +138,17 @@ Each directory resolves to the first match: its environment variable, the instal
|
|
|
134
138
|
| `MCP_RELEASE_NOTES_PATH` | Release notes directory |
|
|
135
139
|
| `MCP_SKILLS_PATH` | Client skills directory layered over the Jaypie skills (namespace `local`) |
|
|
136
140
|
|
|
141
|
+
`getMcpBuildInfo` from `@jaypie/mcp/assets` returns `{ commit, version, versionString }` of the build that packed the markdown, the same values the `version` tool reports. A consumer that syncs the copied directories reads it from the same package without a registry call.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { getMcpBuildInfo } from "@jaypie/mcp/assets";
|
|
145
|
+
|
|
146
|
+
const { commit, version, versionString } = getMcpBuildInfo();
|
|
147
|
+
// versionString: "@jaypie/mcp@0.8.159#460f95c3"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`commit` is the first 8 characters of `PROJECT_COMMIT` at build time and is empty when it was unset. Unbuilt source returns version `0.0.0`.
|
|
151
|
+
|
|
137
152
|
### SDK Transport (`mcpExpressHandler`)
|
|
138
153
|
|
|
139
154
|
`mcpExpressHandler` from `@jaypie/mcp` wraps the SDK's `StreamableHTTPServerTransport` with optional sessions. It suits a long-running Express server, not Lambda.
|
package/skills/migrations.md
CHANGED
|
@@ -47,26 +47,38 @@ new JaypieMigration(this, "SeedData", {
|
|
|
47
47
|
- **Role**: Tagged as `CDK.ROLE.PROCESSING`
|
|
48
48
|
- **Execution**: Uses `cr.Provider` with both `onEventHandler` and `isCompleteHandler` pointing to the same Lambda. The `onEventHandler` returns `PhysicalResourceId` immediately; the migration code runs in `isCompleteHandler` invocations, which are polled by Step Functions until `IsComplete: true`. `Delete` requests skip the migration entirely.
|
|
49
49
|
- **Dependencies**: Use `dependencies` to ensure tables and other resources exist before the migration executes
|
|
50
|
-
- **Permissions**: Tables passed via `tables` get data-plane (`grantReadWriteData`), `Query` and `Scan` on `${tableArn}/index/*`, plus
|
|
51
|
-
- **
|
|
50
|
+
- **Permissions**: Tables passed via `tables` get data-plane (`grantReadWriteData`), `Query` and `Scan` on `${tableArn}/index/*`, plus `DescribeTable`, `DescribeTimeToLive`, and `DescribeContinuousBackups` scoped to the table ARN and its indexes. Migrations read and write data. They do not change table shape
|
|
51
|
+
- **Indexes**: CDK owns indexes, TTL, and backups. Declare every registered `fabricIndex()` in `JaypieDynamoDb` `indexes` (see `skill("dynamodb")`). A GSI created outside CDK blocks every later CloudFormation update to the table, so `JaypieMigration` grants no `UpdateTable`. Synth warns (`@jaypie/constructs:migrationTableWithoutIndexes`) when a `JaypieDynamoDb` in `tables` declares no indexes; acknowledge it for tables that need none
|
|
52
52
|
|
|
53
53
|
## Migration Lambda Handler
|
|
54
54
|
|
|
55
55
|
Use `migrationHandler` from `jaypie` so errors propagate as CFN failures and the waiter protocol is handled automatically.
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
// src/migrations/seed/index.ts
|
|
59
|
-
import { initClient, seedEntities, APEX } from "@jaypie/dynamodb";
|
|
60
|
-
import { migrationHandler } from "jaypie";
|
|
57
|
+
Keep the seed in its own module so the local table script runs the same seed (see Local Development in `skill("dynamodb")`):
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
```typescript
|
|
60
|
+
// src/migrations/seed/seed.ts
|
|
61
|
+
import { APEX, seedEntities } from "@jaypie/dynamodb";
|
|
64
62
|
|
|
65
|
-
|
|
63
|
+
export async function seed() {
|
|
64
|
+
return seedEntities([
|
|
66
65
|
{ alias: "config-main", model: "config", name: "Main Config", scope: APEX },
|
|
67
66
|
{ alias: "vocab-en", model: "vocabulary", name: "English", scope: APEX },
|
|
68
67
|
]);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
69
70
|
|
|
71
|
+
```typescript
|
|
72
|
+
// src/migrations/seed/index.ts
|
|
73
|
+
import "@project/models"; // registers models; indexEntity requires them
|
|
74
|
+
import { initClient } from "@jaypie/dynamodb";
|
|
75
|
+
import { migrationHandler } from "jaypie";
|
|
76
|
+
|
|
77
|
+
import { seed } from "./seed.js";
|
|
78
|
+
|
|
79
|
+
export const handler = migrationHandler(async () => {
|
|
80
|
+
await initClient();
|
|
81
|
+
await seed();
|
|
70
82
|
return { status: "complete" };
|
|
71
83
|
});
|
|
72
84
|
```
|
|
@@ -178,7 +178,8 @@ See `skill("apikey")` for full key generation/validation/hashing documentation.
|
|
|
178
178
|
|
|
179
179
|
```bash
|
|
180
180
|
# First deploy creates secrets and table
|
|
181
|
-
|
|
181
|
+
# PROJECT_NONCE is the environment's 8-hex nonce (see skill("cicd-environments"))
|
|
182
|
+
PROJECT_ENV=sandbox PROJECT_NONCE=3f9c21ab npx cdk deploy
|
|
182
183
|
```
|
|
183
184
|
|
|
184
185
|
No workflow secrets needed for generated values — CloudFormation creates them on first deploy and preserves them on subsequent deploys.
|
package/skills/releasenotes.md
CHANGED
|
@@ -9,12 +9,15 @@ How to access and write release notes for Jaypie packages.
|
|
|
9
9
|
## MCP Tools
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
release_notes("list")
|
|
13
|
+
release_notes("list", { package: "mcp" })
|
|
14
|
+
release_notes("list", { package: "jaypie", since_version: "1.0.0" })
|
|
15
|
+
release_notes("list", { cursor: "..." })
|
|
16
|
+
release_notes("read", { package: "mcp", version: "0.3.4" })
|
|
16
17
|
```
|
|
17
18
|
|
|
19
|
+
`list` returns 50 notes per page by default (`limit`, maximum 200). When more notes exist, the last line is a `Next page` footer with a `cursor` that continues the same listing.
|
|
20
|
+
|
|
18
21
|
## Directory Structure
|
|
19
22
|
|
|
20
23
|
```
|
package/skills/secrets.md
CHANGED
|
@@ -71,7 +71,7 @@ Synth fails fast whenever a declared secret source produces no secret string, so
|
|
|
71
71
|
```typescript
|
|
72
72
|
new JaypieSecret(this, "ApiKey", { value: process.env.MISSING }); // throws ConfigurationError
|
|
73
73
|
new JaypieSecret(this, "ApiKey", { envKey: "MISSING" }); // throws ConfigurationError
|
|
74
|
-
new JaypieSecret(this, "Placeholder"); // allowed:
|
|
74
|
+
new JaypieSecret(this, "Placeholder"); // allowed: CDK generates a random value
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
`JaypieEnvSecret` applies the same guard, except in consumer environments, where the secret is imported rather than created.
|
|
@@ -91,6 +91,8 @@ jobs:
|
|
|
91
91
|
- run: npx cdk deploy
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
An env-sourced value is written into the synthesized template. Copies persist in the deployed stack template, the CDK assets bucket, `cdk.out`, and `cdk diff` output. Use [External Secrets](#external-secrets) for credentials that must stay out of those places.
|
|
95
|
+
|
|
94
96
|
## Runtime: Retrieving Secrets
|
|
95
97
|
|
|
96
98
|
Use `getEnvSecret` to fetch secrets in Lambda:
|
|
@@ -202,6 +204,66 @@ new JaypieEnvSecret(this, "AdminSeed", {
|
|
|
202
204
|
|
|
203
205
|
With `excludePunctuation: true` and `includeSpace: false`, the generated value contains only base62 characters.
|
|
204
206
|
|
|
207
|
+
## External Secrets
|
|
208
|
+
|
|
209
|
+
`external: true` creates an empty secret (no `SecretString` or `GenerateSecretString`), so the value never enters the template, the assets bucket, `cdk.out`, or `cdk diff`. CI sets the value after deploy. `envKey` still names the runtime variable (`SECRET_ANTHROPIC_API_KEY`) but is never read at synth. Combining `external` with `value` or `generateSecretString` throws `ConfigurationError`.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
const anthropicSecret = new JaypieSecret(this, "ANTHROPIC_API_KEY", {
|
|
213
|
+
external: true,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
new JaypieLambda(this, "Handler", {
|
|
217
|
+
code: "dist/lambda",
|
|
218
|
+
handler: "index.handler",
|
|
219
|
+
secrets: [anthropicSecret],
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The construct outputs the secret ARN with the `envKey` (or construct id) as the output description. Set the value in a step after `cdk deploy`, and keep the value out of the deploy step's environment:
|
|
224
|
+
|
|
225
|
+
```yaml
|
|
226
|
+
- name: Set external secrets
|
|
227
|
+
env:
|
|
228
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
229
|
+
STACK_NAME: my-stack
|
|
230
|
+
run: |
|
|
231
|
+
ARN=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" \
|
|
232
|
+
--query "Stacks[0].Outputs[?Description=='ANTHROPIC_API_KEY'].OutputValue" \
|
|
233
|
+
--output text)
|
|
234
|
+
printf '%s' "$ANTHROPIC_API_KEY" > "$RUNNER_TEMP/secret"
|
|
235
|
+
aws secretsmanager put-secret-value --secret-id "$ARN" \
|
|
236
|
+
--secret-string "file://$RUNNER_TEMP/secret" > /dev/null
|
|
237
|
+
rm -f "$RUNNER_TEMP/secret"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
- A new external secret has no value until the step runs; `getEnvSecret` fails until then.
|
|
241
|
+
- CloudFormation never writes the value, so a hand-rotated secret is not overwritten by a later deploy.
|
|
242
|
+
- Switching an existing secret to `external` removes `SecretString` from the template. Run the value step in the same workflow.
|
|
243
|
+
- Each `put-secret-value` creates a secret version. Compare with `get-secret-value` first to skip unchanged writes.
|
|
244
|
+
- In `JaypieEnvSecret` consumer environments the secret is imported; set the value on the provider stack.
|
|
245
|
+
|
|
246
|
+
### SSOSync Credentials
|
|
247
|
+
|
|
248
|
+
`JaypieSsoSyncApplication` writes `googleCredentials` and `scimEndpointAccessToken` into the template and warns at synth (`@jaypie/constructs:ssoSyncLiteralCredentials`). Pass secrets instead:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
new JaypieSsoSyncApplication(this, "SsoSync", {
|
|
252
|
+
googleCredentialsSecret: new JaypieSecret(this, "SsoSyncGoogleCredentials", {
|
|
253
|
+
external: true,
|
|
254
|
+
}),
|
|
255
|
+
scimEndpointAccessTokenSecret: new JaypieSecret(this, "SsoSyncScimToken", {
|
|
256
|
+
external: true,
|
|
257
|
+
}),
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- The application deploys SSOSync as "App only". The construct creates secrets for the admin email, SCIM endpoint URL, region, and identity store ID (still read from `CDK_ENV_SSOSYNC_*`), then passes all six ARNs in `CrossStackConfig`.
|
|
262
|
+
- Both secret props are required together, and neither combines with the literal props.
|
|
263
|
+
- Secrets need a complete ARN: created in the stack or imported with `Secret.fromSecretCompleteArn`. SSOSync reads them with the AWS managed key.
|
|
264
|
+
- Switching an existing application from "App + secrets" deletes the SAR-created `SSOSync*` secrets. Sync fails until CI writes the new secret values. Do not name new secrets `SSOSync*`.
|
|
265
|
+
- SSOSync accepts secret ARNs in `us`, `us-gov`, `ap`, `ca`, `cn`, `eu`, and `sa` regions only.
|
|
266
|
+
|
|
205
267
|
## Seeds and API Keys in Workflows
|
|
206
268
|
|
|
207
269
|
Seeds are secrets used to derive deterministic values (like API keys) at runtime. The generated-secret pattern is ideal for seeds because:
|