First pack tutorial Stable
This tutorial walks the main developer path in a single pass: starting from an
empty directory, you scaffold one problem pack, validate it, publish it pinned to
an immutable Git revision, install it, activate it for a tenant, and pin it to an
event. It uses only one minimal pack (com.example.starter, containing a single
problem, hello-world).
For the pack concept itself, see problem packs; for the full first-deploy picture, see getting started. This page does not restate the contracts (schemas and the diagnostic-code list) — it links to each reference instead.
Just want to run an existing pack? → Use an existing pack.
Prerequisites
Everything in this tutorial up through scaffold → validate → install → activate completes fully offline, with no cloud credentials. Only the step that reaches an actual platform deploy (the final section) requires an AWS account.
- Bun 1.3.11
- A checkout of the repository (
git clonedone). Invoke thepackCLI from the repository root via themake pack-*targets (under the hood it runs./node_modules/.bin/tsx infrastructure/bin/tenkacloud-pack.ts; the CLI's default store./.tenkacloud/pack-storeresolves against the CWD, so repo-root execution is assumed). git2.x or later (used in the step that obtains the pinned revision's SHA)
The CLI is fully offline. It uses no network, no CDK synth, and no cloud credentials.
1. Scaffold the pack
Start from an empty working directory. pack init <dir> generates a pack that
passes validation with zero diagnostics (a manifest, one problem, a provider
artifact placeholder, and a README).
make pack-init ARGS="./my-first-pack"
Expected output:
Pack scaffolded at ./my-first-pack (4 files). Run 'pack validate ./my-first-pack' to check it.
Pass --runtime <provider/engine> to choose the runtime (default: aws/cloudformation).
The supported runtimes are aws/cloudformation / gcp/infra-manager / azure/bicep / sakura/apprun.
The generated manifest (tenkacloud-pack.json)
This is the pack's single entry point. Complete minimal example:
{
"schemaVersion": 1,
"id": "com.example.starter",
"version": "0.1.0",
"core": "^1.0.0",
"title": "Starter pack",
"description": "A scaffolded TenkaCloud problem pack. Edit the metadata and artifact, then validate.",
"license": "Apache-2.0",
"problemsRoot": "problems",
"requiredRuntimes": [
{
"provider": "aws",
"engine": "cloudformation"
}
]
}
The generated problem metadata (problems/challenges/hello-world/metadata.json)
Complete minimal example:
{
"id": "hello-world",
"title": "Hello World",
"category": "challenges",
"description": "A starter problem. Replace this with your own challenge.",
"runtime": {
"provider": "aws",
"engine": "cloudformation",
"entry": "template.yaml"
}
}
For what each field in the manifest and metadata.json means, see problem packs.
2. Validate the pack
pack validate <dir> is the offline validator. It checks the schema, problem-ID
uniqueness, artifact presence, runtime consistency, and more.
make pack-validate ARGS="./my-first-pack"
Expected output:
Pack is valid: 1 problem.
- hello-world
Pass --json when you need machine-readable output.
Exit codes are 0 (success) / 1 (validation failure) / 2 (tool failure: missing directory, missing manifest, misuse).
3. Publish pinned to an immutable Git revision
Publishing means pinning the pack to an immutable Git commit and sharing it.
Branch names, tags, HEAD, and abbreviated hashes are rejected by design.
Only a full 40-hex SHA can be pinned, which guarantees the same pack content can
never change in the future (immutability).
Commit and push the pack to a separate Git repository (outside this core
repository's problems/ tree), then obtain the full SHA of the revision you want
to pin:
git -C ./my-first-pack init
git -C ./my-first-pack add .
git -C ./my-first-pack commit -m "First pack"
git -C ./my-first-pack rev-parse HEAD
Expected output (a 40-digit hex string; the value differs per commit):
3f1c226be0565fa2c90df44e9b1acd5a90e84be2
This SHA is the unit of publication. Push to a remote and distribute the HTTPS URL
plus the SHA. The operational procedure for pinned revisions corresponds to the
runbook in infrastructure/lib/problem-pack/README-external-git-pack.md.
4. Install the pack
You can install from a local directory. Install runs validate → snapshot → lock → dry-run compose atomically and does not activate. A failed install leaves no residue behind.
make pack-install ARGS="./my-first-pack"
Expected output (digest is a hash of the pack contents; the value shown here is
one example for the generated scaffold):
Pack installed: com.example.starter@0.1.0
source: local
digest: b1acd5a90e84be2a2e1ca78f77396ee6c447c9c3f1c226be0565fa2c90df44e9
problems: 1
To install from a pinned Git revision, use install git. An HTTPS URL and a full
40-hex SHA are required:
make pack-install ARGS="git https://github.com/<you>/my-first-pack.git --commit <full-40-hex-sha>"
The lock file (packs-lock.json) records sourceKind: "git", the resolved commit,
the optional subdir, and the content digest. Check installed packs with list:
make pack-list
Expected output:
Installed packs: 1
- com.example.starter@0.1.0 (local, 1 problem, b1acd5a90e84be2a2e1ca78f77396ee6c447c9c3f1c226be0565fa2c90df44e9)
5. Activate for a tenant
Activate an installed immutable revision for a single tenant. Duplicate problem IDs, unavailable runtimes, digest mismatches, and not-installed packs are rejected (exit code 1).
make pack-activate ARGS="com.example.starter@0.1.0 --tenant acme"
Expected output:
Pack activated for tenant 'acme': com.example.starter@0.1.0
Other tenants cannot see it (tenant isolation). Use deactivate to undo.
6. Create an event Preview
Creating an event pins the tenant's effective catalog (core + activated pack
revisions) into an immutable snapshot at creation time. Once pinned, an event's
catalog is never rewritten by a later deactivate / install / activate.
The component that actually pins the event snapshot is the event-creation API (the
Lambda in handlers/event-handler/create.ts). At creation time it burns the
catalogSnapshotId and the pack provenance directly into that event row. The
provenance it uses comes from the provenance map burned in at synth time from the
packs activated at that point (embedded as the BATTLE_PROBLEMS_PROVENANCE
environment variable). createEventSnapshot / EventSnapshotStore in the library
infrastructure/lib/problem-pack/event-pin.ts are an equivalent implementation
that reproduces the same contract offline from the CLI and tests — they are not
the path that actually gets deployed. Every CLI step so far (scaffold → validate →
install → activate) is implemented (stable), but the browser event-creation UI is
platform-layer and is treated as preview on this page.
7. Verify in the organizer console Preview
After creating an event, confirm in the organizer console that the event
references the pinned snapshot and that deployments resolve that event's pinned
pack provenance. As the offline equivalent check,
infrastructure/test/problem-pack/external-git-pack-e2e.test.ts verifies the same
contract against an injected fake transport. In addition,
infrastructure/test/problem-pack/pack-lite-full-chain-e2e.test.ts is a broader
offline E2E that chains everything — from the CLI's scaffold, validate, install,
and activate through Lite's synth wiring, the real event-creation handler, and the
stamping of deploy provenance — as one link of real code only.
8. Teardown
Remove a revision you no longer need with remove. It is refused while an event
or an activation pins that revision (protecting the immutable catalog).
deactivate first, release the pinned references, and then remove.
make pack-deactivate ARGS="com.example.starter@0.1.0 --tenant acme"
./node_modules/.bin/tsx infrastructure/bin/tenkacloud-pack.ts remove com.example.starter@0.1.0
Expected output:
Pack deactivated for tenant 'acme': com.example.starter@0.1.0
Removed com.example.starter@0.1.0.
The pack CLI has no update command. A new version is treated as a separate install.
Common failures → diagnostic codes
On validation failure the CLI prints diagnostics in the [CODE] file:path form.
The code is the contract; the message string is not. For the complete list of
diagnostic codes and their mapping to the public namespaced codes, see
problem packs. Examples you can
actually observe:
| Symptom | Code | Meaning |
|---|---|---|
| The directory does not exist | PACK_DIR_MISSING |
The path you passed contains no pack. Pass a directory that contains tenkacloud-pack.json. |
No tenkacloud-pack.json |
MANIFEST_MISSING |
No manifest at the root. Every pack declares exactly one. |
version is not SemVer |
MANIFEST_INVALID |
e.g. version must be a valid SemVer. Fix tenkacloud-pack.json:version. |
| Duplicate problem IDs | DUPLICATE_PROBLEM_ID |
Problem IDs are not unique within the pack. |
| A missing artifact (template etc.) | ARTIFACT_MISSING |
The file the metadata's runtime.entry points to does not exist. |
| Runtime disagrees with the manifest | RUNTIME_MISMATCH |
The problem's runtime is not in requiredRuntimes. |
For example, breaking version and validating prints:
Pack validation failed: 1 diagnostic(s).
[MANIFEST_INVALID] tenkacloud-pack.json:version
version must be a valid SemVer
PACK_DIR_MISSING / MANIFEST_MISSING / MANIFEST_UNREADABLE exit with code 2
(tool failure); every other validation diagnostic exits with code 1 (validation failure).
Deploy to the platform Preview
From here on you need an AWS account. To put the pinned pack on the platform and
take it all the way to the participant portal, follow Lite mode (make deploy) in
getting started.
Packs are Lite-mode only. Lite's fixed tenantId is local, so activate with
--tenant local (make pack-activate ARGS="com.example.starter@0.1.0 --tenant local"). If you used --tenant acme in the steps above, that was for
offline verification and is not reflected in the cloud deploy as-is. SaaS mode
(make deploy-saas) does not support packs. It explicitly refuses to synth while
even a single activation remains (the escape hatch is
CDK_PARAM_SAAS_IGNORE_PACKS=true, but pack problems then do not appear in the
deploy).
Next steps
- Read about problem packs.
- Start from getting started.
- Browse the API reference.