Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Cordis verification
on:
pull_request:
branches: [cordis]
push:
branches: ['feature/**', 'fix/**']
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: 1.4.4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 24.19.0
- run: bun install --frozen-lockfile
- run: bun run verify
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- Add native API container provider and exact service definition (#617).

### Fixed

- Restore the existing API committer policy to Cordis (#620).
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Cordis Api


## Task 3B: Installed API root and container provider

Legacy src/api/support/server.ts remains this repository's capability. ApiApplication is the default native Cordis root, mounting selected HTTP, operation/worker, Identity and Admin class plugins. Its container provider is also a native class plugin in the host-worker context, with actual server/assets readiness. GET /health/ready is a bounded readiness endpoint; authenticated GET /v1/platform/status and /v1/platform/applications/{id} invoke the public Deployment worker client. Admin/OIDC and /identity routing terminate at the owned HTTPS edge. Local worker authority never arrives in ordinary user input. The API gets only the private worker socket directory, never Docker.sock. The package retains AGPL-3.0 licensing.

Task 3B is authorized after the maintainer reviewed the isolated setup. Tracking: #617. Detailed integration plan: https://github.com/treeseed-ai/platform/blob/cordis/docs/superpowers/plans/2026-10-24-task3b-installed-composition.md. Implementation evidence is recorded separately; these requirements are not a passing-test claim.

Development: Bun 1.4.4, Node 24.19.0, TypeScript 5.9.3, native Cordis 4.0.0-rc.12. Run bun run verify (semantic class/source check, strict compiler, build and nonempty unit/integration/program smoke). Installed acceptance uses genuine exact package/OCI bytes, real identity/database/API/HTTPS and explicit isolated certificate trust. No global trust mutation or legacy service changes. All handwritten source, tools and tests are strict TypeScript classes.

All capabilities that can be plugins, including container services, are native Cordis class plugins with typed swappable contracts. No parallel DI graph. Keep independent repository/public package boundaries, exact archive pins and original licenses. Migration PRs target cordis with Part of issue references. All issues remain open until authorized release; installer/Agent/generalized recovery are later tasks.

Delivery is split into two reviewable changes under #617: the native container provider/service definition baseline, then the API server root and authenticated installed interfaces. Each targets cordis and carries its own checks. The server follow-up consumes the first slice and the exact Identity, Admin and Deployment archives. This baseline does not claim an implemented server.
46 changes: 46 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

139 changes: 139 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
{
"name": "@treeseed-cordis/api",
"version": "0.0.0-cordis.1",
"private": true,
"type": "module",
"license": "AGPL-3.0-only",
"packageManager": "bun@1.4.4",
"engines": {
"node": ">=24.19.0"
},
"exports": {
".": {
"types": "./dist/src/container.d.ts",
"default": "./dist/src/container.js"
},
"./container": {
"types": "./dist/src/container.d.ts",
"default": "./dist/src/container.js"
}
},
"files": [
"dist/src",
"README.md",
"LICENSE"
],
"scripts": {
"check": "bun node_modules/@treeseed-cordis/sdk/dist/src/source-policy.js check",
"build": "bun node_modules/@treeseed-cordis/sdk/dist/src/source-policy.js build && bun dist/src/container.js --metadata",
"test:unit": "bun test ./tests --test-name-pattern '^unit:'",
"test:integration": "bun test ./tests --test-name-pattern '^integration:'",
"test:smoke": "bun test ./tests --test-name-pattern '^smoke:'",
"verify": "bun run check && bun run build && bun run test:unit && bun run test:integration && bun run test:smoke"
},
"peerDependencies": {
"@treeseed-cordis/sdk": "0.0.0-cordis.4"
},
"devDependencies": {
"@treeseed-cordis/sdk": "file:vendor/sdk.tgz",
"typescript": "5.9.3",
"@types/bun": "1.4.4",
"@types/node": "24.10.1"
},
"dependencies": {
"cordis": "4.0.0-rc.12"
},
"cordisCandidate": {
"sdk": {
"repository": "https://github.com/treeseed-ai/sdk.git",
"commit": "c03a2b0eab89786439eddea3cff8161e85676b3e",
"sha256": "d6d0f2f4379d2d95403a226d20899ad22a78a11ccdbb4106116ef0a925295d1b"
}
},
"cordis": {
"id": "node-api",
"pluginId": "node-api",
"capability": "container.api",
"contractVersion": "0.3.0-cordis.1",
"target": "deployment",
"provide": [
"container.api"
],
"inject": [
"deployment.engine",
"container.database",
"container.identity"
]
},
"cordisService": {
"role": "api",
"service": {
"command": [
"--serve",
"--configuration",
"/run/secrets/api-config"
],
"user": "installation-owner",
"workerSocket": true,
"volumes": [],
"secrets": [
{
"source": "api-config",
"target": "/run/secrets/api-config"
}
],
"portTarget": null
},
"slots": {
"api-config": {
"kind": "json",
"value": {
"installationId": {
"$installationId": true
},
"origin": {
"$origin": ""
},
"issuer": {
"$origin": "/identity/realms/cordis"
},
"identityInternalOrigin": "http://identity:8080",
"clientId": "cordis-admin",
"clientSecret": {
"$secret": "oidc-client"
},
"databaseUrl": {
"$template": [
"postgresql://cordis:",
{
"$secret": "database-password"
},
"@database:5432/cordis"
]
},
"bootstrapSubject": {
"$secret": "bootstrap-subject"
},
"workerSocket": "/run/cordis-host/worker.sock",
"assetDigest": {
"$assetDigest": true
}
}
}
},
"readiness": {
"kind": "http-json",
"path": "/health/ready",
"expected": {
"ready": true,
"installationId": {
"$installationId": true
},
"assetDigest": {
"$assetDigest": true
},
"nativeComposition": true
}
}
}
}
20 changes: 20 additions & 0 deletions src/container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {Service} from 'cordis';
import {readFileSync,writeFileSync} from 'node:fs';
import {InstalledSchemas,InstalledDeclarations,InstalledTokens,PlatformSchemas,type ContainerEngine,type ReadyContainer,type ProviderDeclaration} from '@treeseed-cordis/sdk';
import {Context} from '@treeseed-cordis/sdk/runtime';
export class NodeApiContainerPlugin implements ReadyContainer {
static readonly declaration=InstalledDeclarations.service('node-api','container.api',['deployment.engine','container.database','container.identity']);
static readonly definition=InstalledSchemas.definition.parse({role:'api',service:{command:['--serve','--configuration','/run/secrets/api-config'],user:'installation-owner',workerSocket:true,volumes:[],secrets:[{source:'api-config',target:'/run/secrets/api-config'}],portTarget:null},slots:{'api-config':{kind:'json',value:{installationId:{$installationId:true},origin:{$origin:''},issuer:{$origin:'/identity/realms/cordis'},identityInternalOrigin:'http://identity:8080',clientId:'cordis-admin',clientSecret:{$secret:'oidc-client'},databaseUrl:{$template:['postgresql://cordis:',{$secret:'database-password'},'@database:5432/cordis']},bootstrapSubject:{$secret:'bootstrap-subject'},workerSocket:'/run/cordis-host/worker.sock',assetDigest:{$assetDigest:true}}}},readiness:{kind:'http-json',path:'/health/ready',expected:{ready:true,installationId:{$installationId:true},assetDigest:{$assetDigest:true},nativeComposition:true}}});
static readonly provide=this.declaration.provide;static readonly inject=this.declaration.inject;static readonly Config=PlatformSchemas.empty;
private readonly engine:ContainerEngine;
constructor(private readonly context:Context){this.engine=InstalledTokens.engine.parse(Reflect.get(context,InstalledTokens.engine.id));}
async [Service.init]():Promise<void>{await this.engine.start('api',NodeApiContainerPlugin.definition);if(!await this.ready())throw new Error('API readiness failed');this.context.provide(InstalledTokens.api.id,this);}
identity():Readonly<{installationId:string;composition:string}>{return this.engine.identity();}
ready():Promise<boolean>{return this.engine.ready('api');}
}
export {NodeApiContainerPlugin as Provider};
export class ApiContainerBuild {
static metadata():ProviderDeclaration{return NodeApiContainerPlugin.declaration;}
static run():void{if(!import.meta.main)return;if(process.argv[2]==='--metadata'){const metadata=JSON.parse(readFileSync('package.json','utf8')) as Record<string,unknown>;metadata.cordis=this.metadata();metadata.cordisService=NodeApiContainerPlugin.definition;writeFileSync('package.json',JSON.stringify(metadata,null,2)+'\n');}else if(process.argv[2]==='--describe')process.stdout.write(JSON.stringify(this.metadata())+'\n');else{process.stderr.write('Unsupported API container program invocation.\n');process.exitCode=2;}}
}
ApiContainerBuild.run();
11 changes: 11 additions & 0 deletions tests/container.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {test,expect} from 'bun:test';
import {spawnSync} from 'node:child_process';
import {readFileSync} from 'node:fs';
import {ApiContainerBuild,NodeApiContainerPlugin} from '../src/container.js';
import {InstalledSchemas} from '@treeseed-cordis/sdk';
class ApiContainerTests {
program():void{const result=spawnSync('node',['dist/src/container.js','--describe'],{encoding:'utf8'});expect(result.status).toBe(0);expect(result.stderr).toBe('');expect(JSON.parse(result.stdout).capability).toBe('container.api');}
definition():void{const definition=InstalledSchemas.definition.parse(NodeApiContainerPlugin.definition);expect(definition.service.workerSocket).toBe(true);expect(definition.service.command).toEqual(['--serve','--configuration','/run/secrets/api-config']);expect(definition.service.environment).toBeUndefined();expect(NodeApiContainerPlugin.inject).toEqual(['deployment.engine','container.database','container.identity']);}
metadata():void{const metadata=JSON.parse(readFileSync('package.json','utf8'));expect(metadata.cordis).toEqual(ApiContainerBuild.metadata());expect(metadata.cordisService).toEqual(NodeApiContainerPlugin.definition);}
}
const suite=new ApiContainerTests();test('smoke: built API container program declares native provider',suite.program.bind(suite));test('unit: API container declares bounded configuration and dependencies',suite.definition.bind(suite));test('integration: package projects actual container class definition',suite.metadata.bind(suite));
Binary file added vendor/sdk.tgz
Binary file not shown.
Loading