@owlmeans/viable-common 0.0.23 → 0.0.25

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.
Files changed (62) hide show
  1. package/README.md +4 -4
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/agent-presentation/SKILL.md +1 -1
  4. package/agent-meta/skills/viable-common/SKILL.md +10 -9
  5. package/build/blueprint/case.js +3 -3
  6. package/build/connect/consts.d.ts +24 -43
  7. package/build/connect/consts.d.ts.map +1 -1
  8. package/build/connect/consts.js +27 -48
  9. package/build/connect/consts.js.map +1 -1
  10. package/build/connect/entrypoints.d.ts +22 -19
  11. package/build/connect/entrypoints.d.ts.map +1 -1
  12. package/build/connect/entrypoints.js +8 -4
  13. package/build/connect/entrypoints.js.map +1 -1
  14. package/build/connect/index.d.ts +0 -1
  15. package/build/connect/index.d.ts.map +1 -1
  16. package/build/connect/index.js +0 -1
  17. package/build/connect/index.js.map +1 -1
  18. package/build/connect/ops.d.ts +1 -4
  19. package/build/connect/ops.d.ts.map +1 -1
  20. package/build/connect/references.d.ts +20 -16
  21. package/build/connect/references.d.ts.map +1 -1
  22. package/build/connect/references.js +3 -1
  23. package/build/connect/references.js.map +1 -1
  24. package/build/connect/schemas.d.ts +6 -3
  25. package/build/connect/schemas.d.ts.map +1 -1
  26. package/build/connect/schemas.js +4 -4
  27. package/build/connect/schemas.js.map +1 -1
  28. package/build/connect/types.d.ts +36 -66
  29. package/build/connect/types.d.ts.map +1 -1
  30. package/build/convert/schemas.d.ts.map +1 -1
  31. package/build/convert/schemas.js +9 -7
  32. package/build/convert/schemas.js.map +1 -1
  33. package/build/convert/types.d.ts +9 -6
  34. package/build/convert/types.d.ts.map +1 -1
  35. package/build/design/runtime.d.ts +1 -1
  36. package/build/design/runtime.d.ts.map +1 -1
  37. package/build/planning/consts.d.ts +1 -1
  38. package/build/planning/consts.js +1 -1
  39. package/build/skills/catalogue.d.ts.map +1 -1
  40. package/build/skills/catalogue.js +22 -15
  41. package/build/skills/catalogue.js.map +1 -1
  42. package/package.json +8 -8
  43. package/src/blueprint/case.ts +3 -3
  44. package/src/connect/consts.ts +25 -45
  45. package/src/connect/entrypoints.ts +26 -20
  46. package/src/connect/index.ts +0 -1
  47. package/src/connect/ops.ts +1 -4
  48. package/src/connect/references.ts +34 -28
  49. package/src/connect/schemas.ts +8 -8
  50. package/src/connect/types.ts +38 -69
  51. package/src/convert/schemas.ts +9 -7
  52. package/src/convert/types.ts +9 -6
  53. package/src/design/runtime.ts +1 -1
  54. package/src/planning/consts.ts +1 -1
  55. package/src/skills/catalogue.ts +22 -15
  56. package/tests/connect-convert.spec.ts +2 -8
  57. package/tests/connect-entrypoints.spec.ts +11 -29
  58. package/build/connect/jobs.d.ts +0 -25
  59. package/build/connect/jobs.d.ts.map +0 -1
  60. package/build/connect/jobs.js +0 -29
  61. package/build/connect/jobs.js.map +0 -1
  62. package/src/connect/jobs.ts +0 -40
@@ -82,9 +82,6 @@ export const ALIAS_CONVENTION = `
82
82
  \`web:layout:\` alias and no \`app.web.layout\` — writing either addresses nothing.
83
83
  - An endpoint alias is \`api:<entity>:<action>\` — \`api:task:list\`, \`api:task:create\`. The
84
84
  group alias that carries the shared path and guard is \`api:<entity>\` — \`api:task\`.
85
- - A queued job alias is \`job:<entity>:<action>\` — \`job:report:build\`, \`job:contract:analyze\`.
86
- A job is never given a \`web:\` or \`api:\` alias, and an endpoint is never given a \`job:\` one:
87
- the prefix is what says which side serves it.
88
85
  - Lowercase kebab-case in every segment. The prefix is NOT optional and NOT decorative: access
89
86
  rules are applied per side by it.
90
87
  `.trim()
@@ -103,11 +100,12 @@ The generated project is a \`@owlmeans/create-app\` monorepo with five workspace
103
100
  - \`sources/web\` — the react application.
104
101
  - \`sources/worker\` — the queue consumer. Same context, no HTTP routes, one processor per job.
105
102
 
106
- These files are the CONTRACT between the packages. Every screen, endpoint and job passes through
107
- them, and each has a sentinel comment marking where a new line goes:
103
+ These files are the CONTRACT between the public packages. Every screen and endpoint passes through
104
+ them; technical worker protocols live in the backend package instead:
108
105
 
109
106
  - \`sources/common/src/consts.ts\` — the \`app\` alias tree.
110
107
  - \`sources/common/src/entrypoints.ts\` — the shared immutable protocol tree (\`appEntrypoints\`).
108
+ - \`sources/backend/src/jobs/index.ts\` — backend-only queue protocols and declarations.
111
109
  - \`sources/api/src/entrypoints.ts\` — server bindings: \`bind\` + one \`handlers<Context>()\` wrap
112
110
  per handler.
113
111
  - \`sources/web/src/entrypoints.ts\` — client bindings (\`bindAll\` + \`bindScreen\`).
@@ -1390,25 +1388,34 @@ the work is invisible and the user is left pressing a button that appears to do
1390
1388
  `),
1391
1389
 
1392
1390
  skill(ViableSkill.WorkerJobs, 'Queues, jobs and processors', `
1393
- Three files have to agree, and a job that exists in two of the three is worse than one that
1391
+ Three server-side bindings have to agree, and a job that exists in two of the three is worse than one that
1394
1392
  exists in none — a declared name nothing processes is a message that piles up, and a processor
1395
1393
  with no declaration is dead code the barrel still imports.
1396
1394
 
1397
- ## 1. The queue — \`sources/backend/src/jobs/index.ts\`
1395
+ ## 1. The protocol and queue — \`sources/backend/src/jobs/index.ts\`
1398
1396
  A queue is an ADDRESS: it says what exists and which job names it accepts. Both the api (which
1399
1397
  enqueues) and the worker (which consumes) read this one list, and a job name the queue does not
1400
- declare is refused at enqueue time.
1398
+ declare is refused at enqueue time. The technical protocol belongs here too; common and web must
1399
+ not import it.
1401
1400
 
1402
1401
  \`\`\`ts
1402
+ export const jobProtocols = {
1403
+ reportBuild: protocol(route(
1404
+ 'job:report:build', '/report-build',
1405
+ queueRoute({ service: APP_WORKER, queue: APP_QUEUE, timeout: 30_000 }),
1406
+ ), contract(typed())),
1407
+ }
1403
1408
  export const queues: QueueDeclaration[] = [
1404
- { name: APP_QUEUE, jobs: [app.job.test, app.job.<name>],
1409
+ { name: APP_QUEUE, jobs: [jobProtocols.reportBuild.alias],
1405
1410
  worker: { concurrency: 4, lockDuration: 60_000 } },
1406
1411
  ]
1407
1412
  \`\`\`
1408
1413
 
1409
- ## 2. The alias and the entrypoint — \`sources/common/src\`
1410
- The job's name IS its entrypoint alias. Declare \`app.job.<name>\` in \`consts.ts\` above the
1411
- sentinel, and the protocol in \`entrypoints.ts\` with \`job()\` from \`@owlmeans/route\`.
1414
+ Use \`job()\` from \`@owlmeans/queue\`, never from the generic route package.
1415
+
1416
+ ## 2. The producer — \`sources/api/src/entrypoints.ts\`
1417
+ Bind \`bindClient(jobProtocols.reportBuild)\`. Public API contracts in common describe the domain
1418
+ operation and do not disclose that the handler delegates to a queue.
1412
1419
 
1413
1420
  ## 3. The processor — \`sources/worker/src/jobs/<name>.ts\`
1414
1421
  A PLAIN exported async function — named exports only, exactly like an endpoint handler. It never
@@ -1421,7 +1428,7 @@ imports \`handlers\` and never calls \`handlers<Context>()\`; that belongs only
1421
1428
  // sources/worker/src/entrypoints.ts — the ONLY wrap
1422
1429
  import * as jobs from '@/jobs/index.js'
1423
1430
  const worker = handlers<Context>()
1424
- bind(protocols.job.reportBuild, worker.request(protocols.job.reportBuild, jobs.handleReportBuildJob))
1431
+ bind(jobProtocols.reportBuild, worker.request(jobProtocols.reportBuild, jobs.handleReportBuildJob))
1425
1432
 
1426
1433
  WRONG — the processor module ALSO calls \`handlers()\` and wraps itself, so the export bound above
1427
1434
  is wrapped a SECOND time. \`tsc\` catches it (\`TS2345 "BoundEntrypointHandler<…> is not
@@ -1430,7 +1437,7 @@ function\`:
1430
1437
 
1431
1438
  // WRONG — sources/worker/src/jobs/report-build.ts
1432
1439
  const worker = handlers<Context>()
1433
- export const handleReportBuildJob = worker.request(protocols.job.reportBuild, async (req, ctx) => { ... })
1440
+ export const handleReportBuildJob = worker.request(jobProtocols.reportBuild, async (req, ctx) => { ... })
1434
1441
 
1435
1442
  It RETURNS its result; throwing a \`ResilientError\` subclass is how a refusal is reported, and the
1436
1443
  class survives the broker.
@@ -1443,7 +1450,7 @@ Two rules with no equivalent on the HTTP side:
1443
1450
  what it created, and say in a comment which of the two this one does.
1444
1451
 
1445
1452
  Enqueue from an endpoint with the same typed call used for HTTP:
1446
- \`context.entrypoint(appEntrypoints.job.<name>).call({ body: data })\`.
1453
+ \`context.entrypoint(jobProtocols.<name>).call({ body: data })\`.
1447
1454
  `),
1448
1455
 
1449
1456
  skill(ViableSkill.TargetAgents, 'LLM agents inside the application', `
@@ -3,7 +3,7 @@ import Ajv from 'ajv'
3
3
  import addFormats from 'ajv-formats'
4
4
  import { RouteMethod, route } from '@owlmeans/route'
5
5
  import { aliasOf, openProtocol, protocols } from '@owlmeans/entrypoint'
6
- import { connect, ConnectInquiryKind, ConnectJobKind, ConnectOpKind } from '../src/connect/consts.js'
6
+ import { connect, ConnectInquiryKind, ConnectOpKind } from '../src/connect/consts.js'
7
7
  import { connectProtocols } from '../src/connect/entrypoints.js'
8
8
  import { connectRef } from '../src/connect/references.js'
9
9
  import {
@@ -151,12 +151,6 @@ describe('viable-common - the conversion additions to the connector contract', (
151
151
  // while the two enums stay byte-identical — `manager-api` does not depend on the model
152
152
  // runtime, so nothing else in the stack would notice one of them gaining a fourth shape.
153
153
  expect(Object.values(ConnectInquiryKind)).toEqual(Object.values(InquiryKind))
154
- // One job kind per stage: each stage is its own pipeline run, looked up by the alias its kind
155
- // names, so a single `convert` kind would match whichever run row happened to be newest.
156
- expect(ConnectJobKind.ConvertIntake).toBe('convert-intake')
157
- expect(ConnectJobKind.ConvertAnalysis).toBe('convert-analysis')
158
- expect(ConnectJobKind.ConvertExtraction).toBe('convert-extraction')
159
- expect(ConnectJobKind.ConvertImplementation).toBe('convert-implementation')
160
- expect(ConnectJobKind.ConvertPurge).toBe('convert-purge')
154
+ expect(connect.convert.status).toBe('viable:manager-api:connect:convert:status')
161
155
  })
162
156
  })
@@ -2,9 +2,8 @@ import { describe, expect, test } from 'bun:test'
2
2
  import { openProtocol, protocols } from '@owlmeans/entrypoint'
3
3
  import { DEFAULT_GUARD } from '@owlmeans/auth-common'
4
4
  import { route } from '@owlmeans/route'
5
- import { connect, ConnectJobKind } from '../src/connect/consts.js'
5
+ import { connect } from '../src/connect/consts.js'
6
6
  import { connectProtocols } from '../src/connect/entrypoints.js'
7
- import { JOB_SEPARATOR, jobIdOf, parseJobId } from '../src/connect/jobs.js'
8
7
  import { connectRef } from '../src/connect/references.js'
9
8
 
10
9
  describe('@owlmeans/viable-common — connector protocol tree', () => {
@@ -18,6 +17,7 @@ describe('@owlmeans/viable-common — connector protocol tree', () => {
18
17
  expect(tree.session.open.alias).toBe(connect.session.open)
19
18
  expect(tree.project.llm.alias).toBe(connect.project.llm)
20
19
  expect(tree.project.converterLlm.alias).toBe(connect.project.converterLlm)
20
+ expect(tree.story.status.alias).toBe(connect.story.status)
21
21
  expect(tree.convert.proceed.alias).toBe(connect.convert.proceed)
22
22
  expect(tree.inquiry.answer.alias).toBe(connect.inquiry.answer)
23
23
  expect(tree.files.list.alias).toBe(connect.files.list)
@@ -25,33 +25,15 @@ describe('@owlmeans/viable-common — connector protocol tree', () => {
25
25
  expect(new Set(protocols(tree).map(protocol => protocol.alias)).size).toBe(32)
26
26
  })
27
27
 
28
- test('carries no story routes: stories are planning cards served by the planning tree', () => {
29
- expect((tree as Record<string, unknown>).story).toBeUndefined()
30
- expect((connect as Record<string, unknown>).story).toBeUndefined()
31
- expect((connectRef as unknown as Record<string, unknown>).story).toBeUndefined()
32
- expect(protocols(tree).some(protocol => protocol.alias.includes(':story:'))).toBe(false)
28
+ test('keeps story mutations in planning and exposes only the composed story status', () => {
29
+ expect(Object.keys(tree.story)).toEqual(['status'])
30
+ expect(Object.keys(connect.story)).toEqual(['status'])
31
+ expect(Object.keys(connectRef.story)).toEqual(['status'])
32
+ expect(tree.story.status.route.route.path).toBe('/project/:id/story/:storyId/status')
33
33
  })
34
- })
35
-
36
- describe('@owlmeans/viable-common — connector job ids', () => {
37
- test('a story job id names the card and splits back into its parts', () => {
38
- const id = jobIdOf(ConnectJobKind.StoryDevelop, 'project-card', 'story-card')
39
-
40
- expect(id).toBe(['story-develop', 'project-card', 'story-card'].join(JOB_SEPARATOR))
41
- expect(parseJobId(id)).toEqual({
42
- kind: ConnectJobKind.StoryDevelop, projectId: 'project-card', suffix: 'story-card',
43
- })
44
- })
45
-
46
- test('a job with no subject carries no suffix', () => {
47
- expect(parseJobId(jobIdOf(ConnectJobKind.ProjectInit, 'p1'))).toEqual({
48
- kind: ConnectJobKind.ProjectInit, projectId: 'p1',
49
- })
50
- })
51
-
52
- test('a suffix that contains the separator is rejoined, never truncated', () => {
53
- const suffix = `run${JOB_SEPARATOR}tail`
54
-
55
- expect(parseJobId(jobIdOf(ConnectJobKind.PipelineResume, 'p1', suffix)).suffix).toBe(suffix)
34
+ test('has no generic connector job endpoint', () => {
35
+ expect((tree.project as Record<string, unknown>).job).toBeUndefined()
36
+ expect((connect.project as Record<string, unknown>).job).toBeUndefined()
37
+ expect((connectRef.project as Record<string, unknown>).job).toBeUndefined()
56
38
  })
57
39
  })
@@ -1,25 +0,0 @@
1
- import type { ConnectJobKind } from './consts.js';
2
- /**
3
- * The separator a job id is joined with.
4
- *
5
- * `~`, because every other separator in play is already spoken for: a route alias is
6
- * colon-delimited and so is every run id (`init:<projectId>`, `story:<storyId>`), so a job id
7
- * built with one could not be split back apart.
8
- */
9
- export declare const JOB_SEPARATOR = "~";
10
- /**
11
- * A job's id: its kind, the project card it belongs to, and the subject it is about.
12
- *
13
- * The suffix is a CARD id for a story job and a run id for a pipeline job — never a story code.
14
- * Declared here rather than by whoever answers the job, because the SDK composes the id it then
15
- * polls for: two spellings of one id is a job nobody can find.
16
- */
17
- export declare const jobIdOf: (kind: ConnectJobKind, projectId: string, suffix?: string) => string;
18
- export interface ParsedJobId {
19
- kind: ConnectJobKind;
20
- projectId: string;
21
- suffix?: string;
22
- }
23
- /** Split a job id back into what {@link jobIdOf} composed it from. */
24
- export declare const parseJobId: (jobId: string) => ParsedJobId;
25
- //# sourceMappingURL=jobs.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../src/connect/jobs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,MAAM,CAAA;AAEhC;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,SAAU,cAAc,aAAa,MAAM,WAAW,MAAM,KAAG,MACP,CAAA;AAE5E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,cAAc,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,sEAAsE;AACtE,eAAO,MAAM,UAAU,UAAW,MAAM,KAAG,WAW1C,CAAA"}
@@ -1,29 +0,0 @@
1
- /**
2
- * The separator a job id is joined with.
3
- *
4
- * `~`, because every other separator in play is already spoken for: a route alias is
5
- * colon-delimited and so is every run id (`init:<projectId>`, `story:<storyId>`), so a job id
6
- * built with one could not be split back apart.
7
- */
8
- export const JOB_SEPARATOR = '~';
9
- /**
10
- * A job's id: its kind, the project card it belongs to, and the subject it is about.
11
- *
12
- * The suffix is a CARD id for a story job and a run id for a pipeline job — never a story code.
13
- * Declared here rather than by whoever answers the job, because the SDK composes the id it then
14
- * polls for: two spellings of one id is a job nobody can find.
15
- */
16
- export const jobIdOf = (kind, projectId, suffix) => [kind, projectId, ...(suffix != null ? [suffix] : [])].join(JOB_SEPARATOR);
17
- /** Split a job id back into what {@link jobIdOf} composed it from. */
18
- export const parseJobId = (jobId) => {
19
- const [kind, projectId, ...rest] = jobId.split(JOB_SEPARATOR);
20
- // A suffix may carry the separator back if one ever ends up in a card or run id, so the tail is
21
- // rejoined rather than taken as its first segment.
22
- const suffix = rest.length > 0 ? rest.join(JOB_SEPARATOR) : undefined;
23
- return {
24
- kind: kind,
25
- projectId: projectId ?? '',
26
- ...(suffix != null ? { suffix } : {}),
27
- };
28
- };
29
- //# sourceMappingURL=jobs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../src/connect/jobs.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAA;AAEhC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAoB,EAAE,SAAiB,EAAE,MAAe,EAAU,EAAE,CAC1F,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;AAQ5E,sEAAsE;AACtE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAa,EAAe,EAAE;IACvD,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;IAC7D,gGAAgG;IAChG,mDAAmD;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAErE,OAAO;QACL,IAAI,EAAE,IAAsB;QAC5B,SAAS,EAAE,SAAS,IAAI,EAAE;QAC1B,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtC,CAAA;AACH,CAAC,CAAA"}
@@ -1,40 +0,0 @@
1
- import type { ConnectJobKind } from './consts.js'
2
-
3
- /**
4
- * The separator a job id is joined with.
5
- *
6
- * `~`, because every other separator in play is already spoken for: a route alias is
7
- * colon-delimited and so is every run id (`init:<projectId>`, `story:<storyId>`), so a job id
8
- * built with one could not be split back apart.
9
- */
10
- export const JOB_SEPARATOR = '~'
11
-
12
- /**
13
- * A job's id: its kind, the project card it belongs to, and the subject it is about.
14
- *
15
- * The suffix is a CARD id for a story job and a run id for a pipeline job — never a story code.
16
- * Declared here rather than by whoever answers the job, because the SDK composes the id it then
17
- * polls for: two spellings of one id is a job nobody can find.
18
- */
19
- export const jobIdOf = (kind: ConnectJobKind, projectId: string, suffix?: string): string =>
20
- [kind, projectId, ...(suffix != null ? [suffix] : [])].join(JOB_SEPARATOR)
21
-
22
- export interface ParsedJobId {
23
- kind: ConnectJobKind
24
- projectId: string
25
- suffix?: string
26
- }
27
-
28
- /** Split a job id back into what {@link jobIdOf} composed it from. */
29
- export const parseJobId = (jobId: string): ParsedJobId => {
30
- const [kind, projectId, ...rest] = jobId.split(JOB_SEPARATOR)
31
- // A suffix may carry the separator back if one ever ends up in a card or run id, so the tail is
32
- // rejoined rather than taken as its first segment.
33
- const suffix = rest.length > 0 ? rest.join(JOB_SEPARATOR) : undefined
34
-
35
- return {
36
- kind: kind as ConnectJobKind,
37
- projectId: projectId ?? '',
38
- ...(suffix != null ? { suffix } : {}),
39
- }
40
- }