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
9 changes: 9 additions & 0 deletions apps/hub/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,7 @@ export async function createHub(config: HubConfig) {
registry: createWorkflowAuthorRegistry({
db,
assetService,
repoStore: agentRepoStore.repoStore,
grantStore: chatGrantStore,
conditionRegistry: chatConditionRegistry,
}),
Expand Down Expand Up @@ -3104,6 +3105,14 @@ export async function createHub(config: HubConfig) {
connectorId: "interaction",
credentialBinding: null,
});
// Workflow-source authoring needs no credential either: every write is
// authorized against the run's own asset grants by
// `/api/workflow-workflow-authoring` (mounted above).
entries.push({
name: "@corbits/workflow-authoring-tools",
connectorId: "workflow-authoring",
credentialBinding: null,
});
return entries;
}

Expand Down
5 changes: 5 additions & 0 deletions apps/sidecar/src/workflow-substrate-factory/step-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ export function createSidecarStepBuildEnv(
hubCatalogUrl: string;
hubAgentDirectoryUrl: string;
hubChatUrl: string;
hubWorkflowAuthoringUrl: string;
sidecarToken: string;
definitionId: string;
} = {
Expand Down Expand Up @@ -421,6 +422,10 @@ export function createSidecarStepBuildEnv(
hubCatalogUrl: deps.hubArtifactsUrl,
hubAgentDirectoryUrl: deps.hubArtifactsUrl,
hubChatUrl: deps.hubArtifactsUrl,
// And under the key `@corbits/workflow-authoring-tools` declares
// (`requires: ["hubWorkflowAuthoringUrl", "sidecarToken", "address"]`)
// for `@corbits/agent-workflow-authoring`'s run-authenticated routes.
hubWorkflowAuthoringUrl: deps.hubArtifactsUrl,
sidecarToken: deps.sidecarToken,
definitionId: deps.definitionId,
};
Expand Down
17 changes: 16 additions & 1 deletion bun.lock

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

32 changes: 26 additions & 6 deletions docs/workflow-source-authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ not what an agent authors by hand.
| ---- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------- |
| 1 | `POST /api/workflow-workflow-authoring/author` (`@corbits/agent-workflow-authoring`) → `AssetService.createAsset` + `populateAsset` | Run bearer + run address → tenant/principal; `asset:*`/`create` | `{ assetId, name, commitSha }` |
| 1' | `.../republish` → `populateAsset` on `refs/heads/main` | `asset:<assetId>`/`write`, own-tenant row check first | `{ assetId, name, commitSha }` |
| 1'' | `GET .../:assetId/source` → `RepoStore.resolveRef` + `openCommittedReads` on `refs/heads/main` | `asset:<assetId>`/`read`, own-tenant row check first | `{ assetId, name, headSha, files }` |
| 2 | Preview: native probe with empty `ApprovalSet`, no freeze | Same run scope | `{ wireHash, grants[] }` or an invalid-package error |
| 3 | `POST /api/tenants/:tenantId/workflows/deployments` | `workflow:*`/`create`; parked on `approval: "ask"` when agent-initiated | `WorkflowDeploymentResponse { id, definitionAssetId, status }` |
| 4 | Human resolves the parked approval (native `approvals` route) | `approval:*`/`resolve` | Deploy continues or is rejected |
Expand Down Expand Up @@ -77,8 +78,16 @@ recorded initiating principal.
- Asset identity is the asset id; the human-readable name is unique per
tenant (`duplicate_asset` → 409 conflict).
- A republish carries `expectedHeadSha`. If the ref moved, the write is
rejected with 409 and the current head; the caller re-reads and retries.
Nothing is silently overwritten.
rejected with 409 and the current head (`currentHeadSha` beside the error
envelope); the caller re-reads and retries. Nothing is silently
overwritten. The check is a read-then-write against `RepoStore.resolveRef`
rather than a compare-and-set inside `writeTree` — `receivePack` has CAS,
`writeTree` does not — so two republishes racing inside that window are
serialized by the repo lock, not refused.
- `populateAsset` is additive. A republish overwrites the paths it names and
carries every other committed file forward; `workflow_source_read` shows
the whole resulting tree. Deleting a file needs a seam that does not exist
yet.
- Writing an identical tree is a no-op commit (content-aware, like the CLI
pusher). Retrying an `author` after a network failure hits
`duplicate_asset`; the caller then republishes.
Expand Down Expand Up @@ -119,13 +128,24 @@ sequenceDiagram
R-->>H: definition selectable as routine target
```

## Seams that exist

- `@corbits/workflow-authoring-tools` (CL-7360): `workflow_author`,
`workflow_republish`, `workflow_source_read` over the routes above,
pinned into Myra's `ASSISTANT_TOOL_PACKAGE_PINS` and published to the
`corbits-tools` registry.
- Path/package validation in `agent-workflow-authoring`'s registry
(`validateWorkflowSourceTree`, CL-7360): runs before any grant check or
write; caps are `MAX_SOURCE_FILE_BYTES`, `MAX_SOURCE_TREE_BYTES`,
`MAX_SOURCE_FILE_COUNT`.

## Seams that do not exist yet (and where they go)

- An `@intx/agent` tool bundle over the authoring routes
(`@corbits/workflow-authoring-tools`): CL-7360.
- A run-authenticated preview route and the `workflow_deploy` tool: CL-7361,
CL-7362.
- Path/package validation in `agent-workflow-authoring`'s registry:
CL-7360.
- Deleting a file from an authored asset (a `writeTreeDelta`-backed
republish, or a `clearPrefix` the substrate accepts at the root).
- A compare-and-set republish (`expectedHeadSha` enforced under the repo
lock rather than before it).

Nothing here adds a repository, compiler, probe, freezer, or approval store.
22 changes: 22 additions & 0 deletions packages/agent-workflow-authoring/src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type WorkflowAuthorErrorReason =
"forbidden" | "not_found" | "conflict" | "invalid";

export class WorkflowAuthorError extends Error {
readonly reason: WorkflowAuthorErrorReason;
/** Set on a `conflict` raised by an `expectedHeadSha` mismatch: the sha
* `refs/heads/main` actually points at, so the caller can re-read and
* retry against it. */
readonly currentHeadSha?: string;
constructor(
reason: WorkflowAuthorErrorReason,
message: string,
options: { readonly currentHeadSha?: string } = {},
) {
super(message);
this.name = "WorkflowAuthorError";
this.reason = reason;
if (options.currentHeadSha !== undefined) {
this.currentHeadSha = options.currentHeadSha;
}
}
}
12 changes: 10 additions & 2 deletions packages/agent-workflow-authoring/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
export { WorkflowAuthorError, type WorkflowAuthorErrorReason } from "./errors";
export {
createWorkflowAuthorRegistry,
WorkflowAuthorError,
WORKFLOW_ASSET_NAME_PATTERN,
type AuthorWorkflowInput,
type CreateWorkflowAuthorRegistryDeps,
type RepublishWorkflowInput,
type WorkflowAssetSummary,
type WorkflowAuthorCaller,
type WorkflowAuthorErrorReason,
type WorkflowAuthorRegistry,
type WorkflowAuthorRepoReads,
type WorkflowSourceSnapshot,
} from "./registry";
export {
MAX_SOURCE_FILE_BYTES,
MAX_SOURCE_FILE_COUNT,
MAX_SOURCE_TREE_BYTES,
validateWorkflowSourceTree,
type ValidatedWorkflowSourceTree,
} from "./source-tree";
export {
createWorkflowAuthorRoutes,
type CreateWorkflowAuthorRoutesDeps,
Expand Down
Loading
Loading