The Project-scoped AppThrust console for building, releasing, sharing, and opening business applications. The Go server serves the React SPA, the console API, and a same-origin Platform API proxy. Directory owns people, groups, and memberships; Builder Console keeps app metadata, access requests, comments, and sharing policy.
cmd/server/: Go server and tests, usinggithub.com/appthrust/appserver-go v0.2.0.web/: Vite/React SPA, unit tests, in-memory development APIs, and Playwright stories.catalog/entry.yaml: ApplicationStackDefinition, including the Directory dependency, PostgreSQL connection, identity access settings, probes, and existing version floors.hack/publish-entry.ts: immutable version rendering and signed catalog publication..github/workflows/ci.yml: Go, web, and publisher checks..github/workflows/publish.yml: tag-only multi-architecture image and catalog publisher.
Imported from appthrust/platform revision
5536b5065bcd571f59078eae71a0269a65294e6f, paths
cmd/builder-console-server and dashboard/packages/builder-console.
The app no longer needs a Platform source checkout. See web/README.md
for UI architecture, development modes, and product contracts.
Use Go 1.26 or newer, Bun 1.4.1 or newer, and a GitHub token with package read access.
web/.npmrc routes the @appthrust scope to GitHub Packages; tokens are supplied only
through NODE_AUTH_TOKEN. All three published packages (api-schema, app-launcher,
platform-dashboard-i18n) use ^0.1.1. The console has its own Japanese-default
language store; the shared i18n package is used by the app launcher. API schema imports
use the published /platform-api and /platform-api-router exports.
go build ./...
go test ./...
go vet ./...
bun test hack/
cd web
NODE_AUTH_TOKEN="$(gh auth token)" bun install --frozen-lockfile
bun run build:image
bun run typecheck
bun run testPostgreSQL integration tests are opt-in: set BUILTIN_APPSERVER_TEST_DATABASE_URL
(or BUILDER_CONSOLE_TEST_DATABASE_URL) to a disposable test database. The Go SDK
creates and cleans an isolated schema per test; without a DSN those tests skip.
DOM unit tests use the repository-local web/test/dom.ts preload from
web/bunfig.toml, with no sibling Dashboard package dependency.
From web/, run bun run mock and bun run dev in separate terminals. The default
ports are 54385 for the mock API and 5199 for Vite; set CONSOLE_MOCK_PORT and
CONSOLE_PORT to isolate concurrent work. Neither command requires a live Platform.
The retained browser suite runs against its own local mock API and Vite server:
cd web
bun run typecheck:e2e
bun x playwright install chromium
bun run test:e2e:mocktest:e2e runs the complete story suite. Configure CONSOLE_E2E_PORT and
CONSOLE_E2E_MOCK_PORT to isolate browser runs. No live platform credentials are
needed. CI runs every story on each pull request and main push (console-mock-stories,
aggregated with the other jobs into the required console-verification check); see
Browser stories for the automatic checks and how to
declare an expected failure. Stories may import only types from
@appthrust/api-schema: its published ESM files have extensionless relative imports
that Playwright's Node loader cannot resolve, and one value import fails discovery
of the whole suite. Fixing the package's Node-ESM exports is owned by the SDK
repository, not by an app-local source alias or loader shim.
web/src/api/contracts/console/schema/ is the canonical Zod source for Console
success wires and the HTTP error envelope. operations.ts in that directory
registers all 33 SPA operations, including /config, screenshot multipart
responses, and the three 204 deletes. Adapters in the parent directory reuse
those schemas and expose the domain types in model/contracts/console.ts.
Feature migrations remain separate; this slice does not replace the unverified
facade's callers.
| Operation family | Count | Responses |
|---|---|---|
| Bootstrap | 2 | me, config |
| Directory proxy | 6 | groups, people, person profile, create person, members, realm users |
| Apps and resolution | 11 | list/get/by-path/create/update/bind/delete, directory list/entry, tools, resolve |
| Comments | 3 | list, post, delete |
| Screenshots | 3 | multipart upload, reorder, delete |
| Access requests | 6 | mine, create, pending, app requests, approve, decline |
| Administration | 2 | platform settings, Directory migration |
Collections carry presence: total; optional observations carry
presence: optionalObservation. There are no blanket null/absence defaults in
the Console readers. Missing permissions fail bootstrap. Unknown share scopes,
roles, and request states remain explicitly unknown; unknown tagged results are
unsupported. A removed person is { unknown: true, id } in the domain, never a
fabricated contact. The older public SDK's broad Person shape is not permission
to invent missing email, subject, or ownership evidence.
Generate and check the internal artifact from web/:
bun run generate:console-schema
bun run check:console-schemacmd/server/internal/api/console-spa.openapi.json is committed, generated, and
not served. Go tests validate real response writers and handler output against
it. testdata/console-wire.json contains Go-marshaled fixtures, including empty
collections, omitted observations, pending people, and removed people; SPA tests
replay those original wires, not parsed/defaulted values. To deliberately update
fixtures after reviewing a writer change:
UPDATE_CONSOLE_WIRE_FIXTURES=1 go test ./cmd/server/internal/api -run '^TestConsoleWriterContract$'The existing served openapi.json remains byte-identical to the public
appserver-go v0.5.0 Console contract. Compatibility tests check its listApps /
getApp fields and fixtures against the internal schema. Replacing that public
document needs separate review. This intentionally deviates from the design's
single generated public document and separate B/api-schema package: the schema
currently has one TypeScript consumer, so it shares the SPA's Zod installation.
A package boundary is deferred until a second consumer needs it, avoiding
file-dependency symlink resolution and build changes.
check:console-schema regenerates into a temporary directory, detects drift,
and cleans up. The existing web CI job runs it. The boundary checker also rejects
feature/model imports of the canonical schema directory, including type-only
imports, barrels, and assertions. PostgreSQL handler contract tests use the same
opt-in DSN and isolated-schema cleanup as the other server integration tests.
The same canonical schema directory now owns build-sessions.ts and
build-session-operations.ts. api/contracts/console/build-sessions.ts exposes
response boundaries and inferred input/domain types; model/contracts/console.ts
exports the domain types. Features must not import raw schemas. These are
declarations only: no v1 handlers, client, database, prompts or UI are registered.
The existing generator also emits cmd/server/internal/domain/build-reply.schema.json
(JSON Schema 2020-12, urn:appthrust:builder-console:reply:1, at most 8 KiB).
Go embeds it through domain.BuildReplySchema() for the later AgentTurn input. Both
artifacts are covered by check:console-schema. The served openapi.json is
unchanged. This deliberately replaces the design's proposed independent
contracts/conversation package and public-document edits with the established
SPA canonical-source/internal-document convention.
Go HTTP DTOs remain in api/build_session_dto.go; reusable Reply types and
the raw validator input live in domain/build_reply.go, following the existing
API-to-domain response ownership. A conversation service can therefore consume
them without depending on its HTTP handlers or creating an import cycle.
All paths below have prefix /api/console/v1. All responses, including errors,
require Cache-Control: no-store. K means required Idempotency-Key
(1–128 printable ASCII characters); V means required strong If-Match: "bs-<version>". Replay lookup precedes version checks; missing preconditions are
428 precondition_required, mismatches 412 version_conflict with
details.currentVersion, and key/body conflicts 409 idempotency_conflict.
| Method/path | Body/query | Success resource | Request headers |
|---|---|---|---|
POST /build-sessions |
kind: new-app, groupId, intent, locale: ja/en |
202 Session | K |
GET /build-sessions |
cursor?, limit? (1–50; default 20) |
200 {items,nextCursor?} |
— |
GET /build-sessions/{id} |
— | 200 Session | — |
GET /build-sessions/resolve |
required draftId |
200 console-v1/sessionId or platform-v1/draftId | — |
POST /build-sessions/{id}/advance |
{} |
202 Session | K, V |
POST /build-sessions/{id}/turns |
conversation or request_plan union | 202 Turn | K, V |
GET /build-sessions/{id}/turns |
cursor?, limit? (1–50; default 20), ordinal order |
200 {items,nextCursor?} |
— |
GET /build-sessions/{id}/turns/{turnId} |
— | 200 Turn | — |
POST /build-sessions/{id}/turns/{turnId}/resolution |
decision: accept_spec/keep_spec |
200 Turn | K, V |
GET /build-sessions/{id}/spec-revisions/{n} |
nonnegative revision | 200 SpecRevision | — |
PATCH /build-sessions/{id}/publish-preferences |
name, shareScope: private/group/company |
202 PublishResult | K, V |
POST /build-sessions/{id}/archive |
{} |
202 Session | K, V |
Session/Turn resource responses and PublishResult return the session ETag;
Turn creation also returns Location. Turns always include sessionVersion,
so resolution returns the updated version without introducing a wrapper.
PublishResult is {desired:{name,shareScope},sessionVersion,sync:{state,failure?}}.
There is no cancel operation and no public share scope in this flow.
Contract choices where the design left a shape or bound implicit:
- Session requires
groupId,locale,createdAt,updatedAtin addition to its illustrated fields;archivedAt,draftId,activeTurnId,failure, andpollAfterMsare optional observations. Dates are RFC3339 UTC (Z). Generated session/turn IDs are UUIDs; Directory and Platform IDs remain opaque. - Versions/ordinals are positive and revisions nonnegative safe JSON integers
(maximum 9007199254740991), including ETags. Revision zero has explicit
sourceTurnId: null; all subsequent revisions require a UUID source. nextActionisnone/observe/advance/confirm/resume. Projection state isnot_requested/pending/syncing/applied/conflict/failed; publish sync ispending/syncing/applied/failed.appliedRevisionis optional, not fabricated. Unknown observed enums become explicit unknown domain values. Writer enums and command unions remain closed; unknown Turn kinds are rejected.- The publish name is nonblank, at most 40 Unicode scalars and one line (CR/LF/NEL/Unicode line separators are rejected); it is never inferred. Human text bounds use Unicode scalars, not UTF-16 units. Initial intent is also limited to 4000 UTF-8 bytes and conversation messages to 16 KiB.
- Reply has exactly the fields of design §6. Text totals include answer,
summary, changes, option labels/descriptions/messages, open topics/wishes,
and nextQuestion, excluding IDs. Both option arrays require unique IDs and
labels. Empty summary is allowed only when
specChanged=false; required human text is nonblank. A changed spec is a proposal, not a committed revision. - Error envelopes stay
{error,message,details?}. Typed details may containfield(JSON pointer),reason,policy,currentVersion,resource(session/operation/workspace/artifact),retryable, storagelimit(files/bytes/entries/depth), andobservedAt. Upstream reason strings such asAgentTurnActive,WorkspaceNotRunning,PolicyPending,DraftVersionChangedandDraftSpecChangedstay reasons, not error codes. Failure codes cover every row in the design's error table plus its precondition/session/input/status errors. Optional short text caps are 128 for reason/policy/prompt version, 256 for field, 512 for opaque IDs, 2048 for cursors, and 200 for each of at most three safe progress activities.
cmd/server/internal/api/testdata/build-conversation.json is the single shared
corpus. Bun and Go enumerate every case (no per-language fixture allowlist),
require every registered Build schema to be represented, and check identical
accept/reject and JSON-pointer paths. It includes APT-752/792 answers/candidates,
mixed edits, undecided names, explicit decisions, repair, open reader enums,
duplicate JSON keys, byte/scalar limits and ETags. Go also decodes accepted
wires into the actual DTOs, marshals them back losslessly, and validates the
writer against the generated schema; adding a schema without a DTO/corpus case
fails. jsonschema/v6 remains test-only.
Standard JSON Schema cannot express UTF-8 byte totals, keyed uniqueness,
display-text sums or comparison with request/spec bytes. The canonical schema
records those as x-* assertions; Zod applies them and the Go parity test
interprets them. BuildReplyValidationInput retains the original reply/spec
bytes and typed expected context for S4-2. The strict JSON contract parser
rejects duplicate keys, invalid UTF-8, trailing JSON and depth above eight.
The S4-2 production validator must still enforce YAML/BuildSpec validity,
artifact provenance/digests and semantic evaluation; this slice does not
claim that schema validation proves natural-language correctness. S4-1/3
must enforce lifecycle transitions, one active Turn, authorization, replay
precedence, immutable attempts and transactional confirmation.
Run the web checks listed in web/package.json, then the server tests/vet,
gofmt on the three new Go files, and go mod tidy -diff. The known APT-845
DSN-only startup tests (TestPublicURLEnvStartup and
TestConsoleMountPrecedesProxyAndMissingDatabase) are unrelated to these
contracts; excluding them does not prove database integration.
NODE_AUTH_TOKEN="$(gh auth token)" docker build --platform linux/amd64 \
--secret id=NODE_AUTH_TOKEN,env=NODE_AUTH_TOKEN -t builder-console-local .The build uses a BuildKit secret for npm authentication and plain go mod download.
The distroless image contains /builder-console-server and the SPA at /srv/console,
runs as UID 65532, and listens on port 8080. No JavaScript runtime or database is
required during the image build.
Platform supplies PLATFORM_API_URL, PLATFORM_PROJECT_ID, and OIDC identity
configuration through the Go SDK. Runtime-specific settings include:
| Setting | Purpose |
|---|---|
BUILDER_CONSOLE_DATABASE_URL / DATABASE_URL |
PostgreSQL connection (the prefixed value takes precedence). |
BUILDER_CONSOLE_PUBLIC_URL |
HTTPS console origin for access-request redirects. |
BUILDER_CONSOLE_DIRECTORY_URL |
Internal Directory dependency URL. |
BUILDER_CONSOLE_DIRECTORY_PUBLIC_URL |
Public Directory URL for user-facing links. |
BUILDER_CONSOLE_STATIC_DIR |
SPA directory; defaults to /srv/console; --static-dir overrides it. |
BUILDER_CONSOLE_DEV_SEED=1 |
Explicit development-only app fixture seeding. |
BUILDER_CONSOLE_RELEASE_VERSION / BUILDER_CONSOLE_ARTIFACT_DIGEST |
Identity reported by /releasez. |
--listen defaults to :8080. /healthz reports liveness; /readyz gates database
readiness. /config, /launcher, /launcher/entries, /api/console/*, /authz,
and the /api/* Platform proxy retain their existing contracts.
Only a v<semver> tag starts publication. The initial standalone release is
v0.3.0, stream 0.3, at ghcr.io/appthrust/builder-console. Do not reuse
0.1.0 or 0.2.0: both served Platform-image floors remain verbatim in the committed
catalog entry. The publisher retains their images, merges persisted release history,
keeps earlier versions served, and makes the new version the only default.
The workflow builds linux/amd64 and linux/arm64, signs the image index with
GitHub OIDC/cosign, then submits the catalog entry to Cloud. A queued publication is
observed directly; a snapshot-only response triggers Cloud publication with the
repository's GitHub App credentials. Success requires the exact version/image digest
in the signed catalog artifact, not merely a successful dispatch.
Required repository secrets: CLOUD_CATALOG_ADMIN_TOKEN,
APPTHRUST_GITHUB_ACTIONS_APP_ID, and APPTHRUST_GITHUB_ACTIONS_APP_PRIVATE_KEY.
Optional variable: CLOUD_API_URL (defaults to https://api.appthrust.com).
GITHUB_TOKEN needs package read/write access to install the published npm SDKs and
push the app image. No secrets belong in this repository.
Existing installations using the Platform image path require one explicit update to
0.3.0 with replaceRepository: true; future updates can follow stream 0.3.
An initial source push does not create a release, publish a catalog, or modify an
installation.