@things-factory/worklist 10.1.3 → 10.1.4

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 (36) hide show
  1. package/dist-client/pages/activity-catalog/activity-catalog-page.d.ts +51 -0
  2. package/dist-client/pages/activity-catalog/activity-catalog-page.js +509 -0
  3. package/dist-client/pages/activity-catalog/activity-catalog-page.js.map +1 -0
  4. package/dist-client/route.d.ts +1 -1
  5. package/dist-client/route.js +3 -0
  6. package/dist-client/route.js.map +1 -1
  7. package/dist-client/tsconfig.tsbuildinfo +1 -1
  8. package/dist-server/controllers/activity-installation/activity-installation-controller.d.ts +35 -0
  9. package/dist-server/controllers/activity-installation/activity-installation-controller.js +23 -0
  10. package/dist-server/controllers/activity-installation/activity-installation-controller.js.map +1 -1
  11. package/dist-server/service/activity/credential-field.d.ts +35 -0
  12. package/dist-server/service/activity/credential-field.js +89 -0
  13. package/dist-server/service/activity/credential-field.js.map +1 -0
  14. package/dist-server/service/activity/credential-guard-subscriber.d.ts +19 -0
  15. package/dist-server/service/activity/credential-guard-subscriber.js +35 -0
  16. package/dist-server/service/activity/credential-guard-subscriber.js.map +1 -0
  17. package/dist-server/service/activity/index.d.ts +2 -1
  18. package/dist-server/service/activity/index.js +2 -1
  19. package/dist-server/service/activity/index.js.map +1 -1
  20. package/dist-server/service/index.d.ts +1 -1
  21. package/dist-server/service/installable-activity/activity-readiness.d.ts +58 -0
  22. package/dist-server/service/installable-activity/activity-readiness.js +105 -0
  23. package/dist-server/service/installable-activity/activity-readiness.js.map +1 -0
  24. package/dist-server/service/installable-activity/installable-activity-query.js +27 -2
  25. package/dist-server/service/installable-activity/installable-activity-query.js.map +1 -1
  26. package/dist-server/service/installable-activity/installable-activity.d.ts +58 -0
  27. package/dist-server/service/installable-activity/installable-activity.js +44 -1
  28. package/dist-server/service/installable-activity/installable-activity.js.map +1 -1
  29. package/dist-server/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +11 -11
  31. package/things-factory.config.js +1 -0
  32. package/translations/en.json +20 -0
  33. package/translations/ja.json +20 -0
  34. package/translations/ko.json +20 -0
  35. package/translations/ms.json +21 -1
  36. package/translations/zh.json +20 -0
@@ -2,6 +2,7 @@ import { EntityManager } from 'typeorm';
2
2
  import { Domain } from '@things-factory/shell';
3
3
  import { Activity } from '../../service/activity/activity.js';
4
4
  import { InstallableActivity } from '../../service/installable-activity/installable-activity.js';
5
+ import { ActivityReadiness } from '../../service/installable-activity/activity-readiness.js';
5
6
  /**
6
7
  * 등록된 양식을 **도메인에 실제로 세운다.**
7
8
  *
@@ -25,13 +26,47 @@ export declare function installActivity(name: string, domain: Domain, options?:
25
26
  manager?: EntityManager;
26
27
  user?: any;
27
28
  }): Promise<Activity>;
29
+ /**
30
+ * Work a module has identified as a person's decision and **has not built.**
31
+ *
32
+ * Deliberately not an InstallableActivity. A template with no callback can be installed and
33
+ * issued, and then someone approves a request and nothing happens — on the twin side that means a
34
+ * person presses approve and no equipment moves. So these live in their own registry: the catalog
35
+ * reads them, `installActivity` cannot reach them.
36
+ *
37
+ * They belong on the screen because the catalog is the plan of record. The question "what is left"
38
+ * should be answered where people look, not in one person's head.
39
+ */
40
+ export type IntendedActivity = {
41
+ /** What the activity's `name` will be once it exists. */
42
+ key: string;
43
+ /** What the catalog shows. The activity's own name is usually not a sentence a person reads. */
44
+ label: string;
45
+ category?: string;
46
+ /** What this work is, for the person who will receive it. Draft wording is fine. */
47
+ guide?: string[];
48
+ readiness: ActivityReadiness;
49
+ };
28
50
  export declare class ActivityInstallations {
29
51
  static templates: {
30
52
  [name: string]: InstallableActivity;
31
53
  };
54
+ /** Keyed separately from `templates`, so nothing here can be installed by mistake. */
55
+ static intended: {
56
+ [key: string]: IntendedActivity;
57
+ };
32
58
  static installActivityTemplate(template: InstallableActivity): void;
59
+ /**
60
+ * Declare work that should reach someone's worklist and does not yet.
61
+ *
62
+ * A module calls this for each item it has identified. When the real thing is built, delete the
63
+ * call and register the template instead — the catalog row moves from 예정 to 동작 확인됨 on its
64
+ * own.
65
+ */
66
+ static declareIntendedActivity(intended: IntendedActivity): void;
33
67
  static get(name: string): InstallableActivity;
34
68
  static list(): InstallableActivity[];
69
+ static listIntended(): IntendedActivity[];
35
70
  /**
36
71
  * 액티비티인스턴스의 변화가 발생했을 때, 호출되는 콜백함수
37
72
  * @param name 액티비티인스턴스가 속한 액티비티명
@@ -48,15 +48,38 @@ async function installActivity(name, domain, options = {}) {
48
48
  }
49
49
  class ActivityInstallations {
50
50
  static { this.templates = {}; }
51
+ /** Keyed separately from `templates`, so nothing here can be installed by mistake. */
52
+ static { this.intended = {}; }
51
53
  static installActivityTemplate(template) {
52
54
  ActivityInstallations.templates[template.name] = template;
53
55
  }
56
+ /**
57
+ * Declare work that should reach someone's worklist and does not yet.
58
+ *
59
+ * A module calls this for each item it has identified. When the real thing is built, delete the
60
+ * call and register the template instead — the catalog row moves from 예정 to 동작 확인됨 on its
61
+ * own.
62
+ */
63
+ static declareIntendedActivity(intended) {
64
+ if (ActivityInstallations.templates[intended.key]) {
65
+ /*
66
+ * Both at once would draw the same work twice and let a reader believe the built one is the
67
+ * plan. Say so rather than picking a winner.
68
+ */
69
+ throw new Error(`declareIntendedActivity: "${intended.key}" is already installed as an activity. ` +
70
+ 'Remove the declaration now that the activity exists.');
71
+ }
72
+ ActivityInstallations.intended[intended.key] = intended;
73
+ }
54
74
  static get(name) {
55
75
  return ActivityInstallations.templates[name];
56
76
  }
57
77
  static list() {
58
78
  return Object.values(ActivityInstallations.templates);
59
79
  }
80
+ static listIntended() {
81
+ return Object.values(ActivityInstallations.intended);
82
+ }
60
83
  /**
61
84
  * 액티비티인스턴스의 변화가 발생했을 때, 호출되는 콜백함수
62
85
  * @param name 액티비티인스턴스가 속한 액티비티명
@@ -1 +1 @@
1
- {"version":3,"file":"activity-installation-controller.js","sourceRoot":"","sources":["../../../server/controllers/activity-installation/activity-installation-controller.ts"],"names":[],"mappings":";;;AA0BA,0CAgCC;AAxDD,iDAA6D;AAE7D,oEAA6E;AAG7E;;;;;;;;;;;;;;;;;;GAkBG;AACI,KAAK,UAAU,eAAe,CACnC,IAAY,EACZ,MAAc,EACd,UAAmD,EAAE;IAErD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAEhD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd;;WAEG;QACH,MAAM,IAAI,KAAK,CACb,4DAA4D,IAAI,KAAK;YACnE,UAAU,qBAAqB,CAAC,IAAI,EAAE;iBACnC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;iBACpB,IAAI,CAAC,IAAI,CAAC,EAAE,CAClB,CAAA;IACH,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IACjC,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAC,sBAAQ,EAAE,OAAO,CAAC,CAAA;IAEnD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;IAEhF,OAAO,MAAM,UAAU,CAAC,IAAI,CAAC;QAC3B,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;QACnB,GAAI,QAAgB;QACpB,KAAK,EAAE,4BAAc,CAAC,QAAQ;QAC9B,MAAM;QACN,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAE,QAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;QACpD,OAAO,EAAE,IAAI;KACP,CAAC,CAAA;AACX,CAAC;AAED,MAAa,qBAAqB;aACzB,cAAS,GAA4C,EAAE,CAAA;IAE9D,MAAM,CAAC,uBAAuB,CAAC,QAA6B;QAC1D,qBAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAA;IAC3D,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,IAAY;QACrB,OAAO,qBAAqB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IAC9C,CAAC;IAED,MAAM,CAAC,IAAI;QACT,OAAO,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAA;IACvD,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,gBAAqB,EAAE,eAAsB,EAAE,OAAwB;QACzG,MAAM,mBAAmB,GAAG,qBAAqB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;QACjE,IAAI,CAAC,mBAAmB,EAAE,CAAC;YACzB,OAAM;QACR,CAAC;QAED,MAAM,EAAE,QAAQ,EAAE,GAAG,mBAAmB,CAAA;QAExC,QAAQ,IAAI,CAAC,MAAM,QAAQ,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,OAAO,CAAC,CAAC,CAAA;IACxF,CAAC;;AAhCH,sDAiCC","sourcesContent":["import { EntityManager } from 'typeorm'\n\nimport { Domain, getRepository } from '@things-factory/shell'\n\nimport { Activity, ActivityStatus } from '../../service/activity/activity.js'\nimport { InstallableActivity } from '../../service/installable-activity/installable-activity.js'\n\n/**\n * 등록된 양식을 **도메인에 실제로 세운다.**\n *\n * ── 왜 함수로 내놓나 ───────────────────────────────────────────────────────\n * 앞서는 이 일이 화면 뮤테이션 안에만 있었다. 그래서 양식을 세우는 길이 **사람이 화면에서 누르는 것\n * 하나뿐**이었고, 새로 배포하거나 도메인을 하나 더 만들 때마다 그 클릭을 기억해야 했다. 기억하지\n * 못하면 그 기능이 오류 보고 없이 없다.\n *\n * 앱이 「내 기능이 서려면 이 양식이 있어야 한다」를 아는 경우가 있다. 그런 앱은 기동할 때 스스로\n * 세울 수 있어야 한다. 언제 세울지는 앱이 정하고, 세우는 방법은 여기가 한 벌만 갖는다.\n *\n * ── 있는 것을 덮되 결재선은 건드리지 않는다 ────────────────────────────────\n * 이미 있으면 양식의 선언(모델 · 보는 화면 · 시작 방식)으로 덮는다. 판이 오르면 그 값이 따라와야\n * 하기 때문이다.\n *\n * 그런데 **결재선은 양식의 것이 아니라 그 현장의 것**이다. 누가 결재하는지는 공장이 정한다. 그래서\n * 양식에 결재선 선언이 없으면 이미 있는 결재선이 그대로 남는다 — 판을 올릴 때마다 결재자가 지워지면\n * 그 뒤로 상신이 곧 완료가 된다.\n */\nexport async function installActivity(\n name: string,\n domain: Domain,\n options: { manager?: EntityManager; user?: any } = {}\n): Promise<Activity> {\n const template = ActivityInstallations.get(name)\n\n if (!template) {\n /*\n * 없는 이름을 세울 수는 없다. 조용히 넘기면 부르는 쪽은 세운 줄 알고, 그 기능은 없는 채로 선다.\n */\n throw new Error(\n `installActivity: there is no installable activity named \"${name}\". ` +\n `known: ${ActivityInstallations.list()\n .map(one => one.name)\n .join(', ')}`\n )\n }\n\n const { manager, user } = options\n const repository = getRepository(Activity, manager)\n\n const existing = await repository.findOneBy({ domain: { id: domain.id }, name })\n\n return await repository.save({\n ...(existing ?? {}),\n ...(template as any),\n state: ActivityStatus.Released,\n domain,\n creator: existing ? (existing as any).creator : user,\n updater: user\n } as any)\n}\n\nexport class ActivityInstallations {\n static templates: { [name: string]: InstallableActivity } = {}\n\n static installActivityTemplate(template: InstallableActivity) {\n ActivityInstallations.templates[template.name] = template\n }\n\n static get(name: string): InstallableActivity {\n return ActivityInstallations.templates[name]\n }\n\n static list(): InstallableActivity[] {\n return Object.values(ActivityInstallations.templates)\n }\n\n /**\n * 액티비티인스턴스의 변화가 발생했을 때, 호출되는 콜백함수\n * @param name 액티비티인스턴스가 속한 액티비티명\n * @param activityInstance 해당 액티비티인스턴스\n * @param activityThreads 액티비티인스턴스의 변화를 일으킨 원인이 되는 액티비티쓰레드들\n * @param context 서비스 컨텍스트 : domain, user, translation, ..\n * @returns\n */\n static async callback(name: string, activityInstance: any, activityThreads: any[], context: ResolverContext) {\n const installableActivity = ActivityInstallations.templates[name]\n if (!installableActivity) {\n return\n }\n\n const { callback } = installableActivity\n\n callback && (await callback(activityInstance, { causedBy: activityThreads }, context))\n }\n}\n"]}
1
+ {"version":3,"file":"activity-installation-controller.js","sourceRoot":"","sources":["../../../server/controllers/activity-installation/activity-installation-controller.ts"],"names":[],"mappings":";;;AA2BA,0CAgCC;AAzDD,iDAA6D;AAE7D,oEAA6E;AAI7E;;;;;;;;;;;;;;;;;;GAkBG;AACI,KAAK,UAAU,eAAe,CACnC,IAAY,EACZ,MAAc,EACd,UAAmD,EAAE;IAErD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAEhD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd;;WAEG;QACH,MAAM,IAAI,KAAK,CACb,4DAA4D,IAAI,KAAK;YACnE,UAAU,qBAAqB,CAAC,IAAI,EAAE;iBACnC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;iBACpB,IAAI,CAAC,IAAI,CAAC,EAAE,CAClB,CAAA;IACH,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IACjC,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAC,sBAAQ,EAAE,OAAO,CAAC,CAAA;IAEnD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;IAEhF,OAAO,MAAM,UAAU,CAAC,IAAI,CAAC;QAC3B,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;QACnB,GAAI,QAAgB;QACpB,KAAK,EAAE,4BAAc,CAAC,QAAQ;QAC9B,MAAM;QACN,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAE,QAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;QACpD,OAAO,EAAE,IAAI;KACP,CAAC,CAAA;AACX,CAAC;AAwBD,MAAa,qBAAqB;aACzB,cAAS,GAA4C,EAAE,CAAA;IAE9D,sFAAsF;aAC/E,aAAQ,GAAwC,EAAE,CAAA;IAEzD,MAAM,CAAC,uBAAuB,CAAC,QAA6B;QAC1D,qBAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAA;IAC3D,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,uBAAuB,CAAC,QAA0B;QACvD,IAAI,qBAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAClD;;;eAGG;YACH,MAAM,IAAI,KAAK,CACb,6BAA6B,QAAQ,CAAC,GAAG,yCAAyC;gBAChF,sDAAsD,CACzD,CAAA;QACH,CAAC;QAED,qBAAqB,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAA;IACzD,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,IAAY;QACrB,OAAO,qBAAqB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IAC9C,CAAC;IAED,MAAM,CAAC,IAAI;QACT,OAAO,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAA;IACvD,CAAC;IAED,MAAM,CAAC,YAAY;QACjB,OAAO,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAA;IACtD,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,gBAAqB,EAAE,eAAsB,EAAE,OAAwB;QACzG,MAAM,mBAAmB,GAAG,qBAAqB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;QACjE,IAAI,CAAC,mBAAmB,EAAE,CAAC;YACzB,OAAM;QACR,CAAC;QAED,MAAM,EAAE,QAAQ,EAAE,GAAG,mBAAmB,CAAA;QAExC,QAAQ,IAAI,CAAC,MAAM,QAAQ,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,OAAO,CAAC,CAAC,CAAA;IACxF,CAAC;;AA7DH,sDA8DC","sourcesContent":["import { EntityManager } from 'typeorm'\n\nimport { Domain, getRepository } from '@things-factory/shell'\n\nimport { Activity, ActivityStatus } from '../../service/activity/activity.js'\nimport { InstallableActivity } from '../../service/installable-activity/installable-activity.js'\nimport { ActivityReadiness } from '../../service/installable-activity/activity-readiness.js'\n\n/**\n * 등록된 양식을 **도메인에 실제로 세운다.**\n *\n * ── 왜 함수로 내놓나 ───────────────────────────────────────────────────────\n * 앞서는 이 일이 화면 뮤테이션 안에만 있었다. 그래서 양식을 세우는 길이 **사람이 화면에서 누르는 것\n * 하나뿐**이었고, 새로 배포하거나 도메인을 하나 더 만들 때마다 그 클릭을 기억해야 했다. 기억하지\n * 못하면 그 기능이 오류 보고 없이 없다.\n *\n * 앱이 「내 기능이 서려면 이 양식이 있어야 한다」를 아는 경우가 있다. 그런 앱은 기동할 때 스스로\n * 세울 수 있어야 한다. 언제 세울지는 앱이 정하고, 세우는 방법은 여기가 한 벌만 갖는다.\n *\n * ── 있는 것을 덮되 결재선은 건드리지 않는다 ────────────────────────────────\n * 이미 있으면 양식의 선언(모델 · 보는 화면 · 시작 방식)으로 덮는다. 판이 오르면 그 값이 따라와야\n * 하기 때문이다.\n *\n * 그런데 **결재선은 양식의 것이 아니라 그 현장의 것**이다. 누가 결재하는지는 공장이 정한다. 그래서\n * 양식에 결재선 선언이 없으면 이미 있는 결재선이 그대로 남는다 — 판을 올릴 때마다 결재자가 지워지면\n * 그 뒤로 상신이 곧 완료가 된다.\n */\nexport async function installActivity(\n name: string,\n domain: Domain,\n options: { manager?: EntityManager; user?: any } = {}\n): Promise<Activity> {\n const template = ActivityInstallations.get(name)\n\n if (!template) {\n /*\n * 없는 이름을 세울 수는 없다. 조용히 넘기면 부르는 쪽은 세운 줄 알고, 그 기능은 없는 채로 선다.\n */\n throw new Error(\n `installActivity: there is no installable activity named \"${name}\". ` +\n `known: ${ActivityInstallations.list()\n .map(one => one.name)\n .join(', ')}`\n )\n }\n\n const { manager, user } = options\n const repository = getRepository(Activity, manager)\n\n const existing = await repository.findOneBy({ domain: { id: domain.id }, name })\n\n return await repository.save({\n ...(existing ?? {}),\n ...(template as any),\n state: ActivityStatus.Released,\n domain,\n creator: existing ? (existing as any).creator : user,\n updater: user\n } as any)\n}\n\n/**\n * Work a module has identified as a person's decision and **has not built.**\n *\n * Deliberately not an InstallableActivity. A template with no callback can be installed and\n * issued, and then someone approves a request and nothing happens — on the twin side that means a\n * person presses approve and no equipment moves. So these live in their own registry: the catalog\n * reads them, `installActivity` cannot reach them.\n *\n * They belong on the screen because the catalog is the plan of record. The question \"what is left\"\n * should be answered where people look, not in one person's head.\n */\nexport type IntendedActivity = {\n /** What the activity's `name` will be once it exists. */\n key: string\n /** What the catalog shows. The activity's own name is usually not a sentence a person reads. */\n label: string\n category?: string\n /** What this work is, for the person who will receive it. Draft wording is fine. */\n guide?: string[]\n readiness: ActivityReadiness\n}\n\nexport class ActivityInstallations {\n static templates: { [name: string]: InstallableActivity } = {}\n\n /** Keyed separately from `templates`, so nothing here can be installed by mistake. */\n static intended: { [key: string]: IntendedActivity } = {}\n\n static installActivityTemplate(template: InstallableActivity) {\n ActivityInstallations.templates[template.name] = template\n }\n\n /**\n * Declare work that should reach someone's worklist and does not yet.\n *\n * A module calls this for each item it has identified. When the real thing is built, delete the\n * call and register the template instead — the catalog row moves from 예정 to 동작 확인됨 on its\n * own.\n */\n static declareIntendedActivity(intended: IntendedActivity) {\n if (ActivityInstallations.templates[intended.key]) {\n /*\n * Both at once would draw the same work twice and let a reader believe the built one is the\n * plan. Say so rather than picking a winner.\n */\n throw new Error(\n `declareIntendedActivity: \"${intended.key}\" is already installed as an activity. ` +\n 'Remove the declaration now that the activity exists.'\n )\n }\n\n ActivityInstallations.intended[intended.key] = intended\n }\n\n static get(name: string): InstallableActivity {\n return ActivityInstallations.templates[name]\n }\n\n static list(): InstallableActivity[] {\n return Object.values(ActivityInstallations.templates)\n }\n\n static listIntended(): IntendedActivity[] {\n return Object.values(ActivityInstallations.intended)\n }\n\n /**\n * 액티비티인스턴스의 변화가 발생했을 때, 호출되는 콜백함수\n * @param name 액티비티인스턴스가 속한 액티비티명\n * @param activityInstance 해당 액티비티인스턴스\n * @param activityThreads 액티비티인스턴스의 변화를 일으킨 원인이 되는 액티비티쓰레드들\n * @param context 서비스 컨텍스트 : domain, user, translation, ..\n * @returns\n */\n static async callback(name: string, activityInstance: any, activityThreads: any[], context: ResolverContext) {\n const installableActivity = ActivityInstallations.templates[name]\n if (!installableActivity) {\n return\n }\n\n const { callback } = installableActivity\n\n callback && (await callback(activityInstance, { causedBy: activityThreads }, context))\n }\n}\n"]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * **This framework cannot hold a credential yet, so it refuses to be handed one.**
3
+ *
4
+ * An activity's model decides what goes into `ActivityInstance.input`, and that column is
5
+ * `simple-json` in the clear. There is no way to mark a field as secret, nothing masks it on the
6
+ * approval screen, the payload copy button copies whatever is there, and the two history entities
7
+ * keep their own copy of the same value. A token entered today would be readable in four places.
8
+ *
9
+ * Nothing declares one right now — every registered activity across both repos was checked and
10
+ * none has a credential-shaped field. So this is a door being closed while it costs nothing, not
11
+ * a leak being patched.
12
+ *
13
+ * ── Why refuse instead of building the sealing (2026-09-09) ───────────────
14
+ * The architect lane ruled the shape: a `secret?: boolean` flag on the model item (not a new
15
+ * ActivityModelItemType — "how is this typed in" and "is this a secret" are different axes, and a
16
+ * renderer missing the new case would fall through `default` and print the value), sealing at the
17
+ * value level rather than the column (the column is filtered on, and sealing it whole would kill
18
+ * that), and the stored shape travelling outward unchanged so there is nothing left to mask.
19
+ *
20
+ * Built now, that machinery would harden with no one using it. Built at first real use, all three
21
+ * parts land together. Until then this refusal keeps the gap from being walked into.
22
+ *
23
+ * It has to run at save time, not in a test: models are authored on screen, so no test sees them.
24
+ */
25
+ /** Does this field name say it holds a credential? */
26
+ export declare function looksLikeCredential(name: string): boolean;
27
+ /**
28
+ * Refuse a model that declares a credential field, and say what to do about it.
29
+ *
30
+ * The message names the field, because a model can carry many and "one of them" would send the
31
+ * author looking.
32
+ */
33
+ export declare function assertNoCredentialField(model: {
34
+ name?: string;
35
+ }[] | undefined, activityName?: string): void;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ /**
3
+ * **This framework cannot hold a credential yet, so it refuses to be handed one.**
4
+ *
5
+ * An activity's model decides what goes into `ActivityInstance.input`, and that column is
6
+ * `simple-json` in the clear. There is no way to mark a field as secret, nothing masks it on the
7
+ * approval screen, the payload copy button copies whatever is there, and the two history entities
8
+ * keep their own copy of the same value. A token entered today would be readable in four places.
9
+ *
10
+ * Nothing declares one right now — every registered activity across both repos was checked and
11
+ * none has a credential-shaped field. So this is a door being closed while it costs nothing, not
12
+ * a leak being patched.
13
+ *
14
+ * ── Why refuse instead of building the sealing (2026-09-09) ───────────────
15
+ * The architect lane ruled the shape: a `secret?: boolean` flag on the model item (not a new
16
+ * ActivityModelItemType — "how is this typed in" and "is this a secret" are different axes, and a
17
+ * renderer missing the new case would fall through `default` and print the value), sealing at the
18
+ * value level rather than the column (the column is filtered on, and sealing it whole would kill
19
+ * that), and the stored shape travelling outward unchanged so there is nothing left to mask.
20
+ *
21
+ * Built now, that machinery would harden with no one using it. Built at first real use, all three
22
+ * parts land together. Until then this refusal keeps the gap from being walked into.
23
+ *
24
+ * It has to run at save time, not in a test: models are authored on screen, so no test sees them.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.looksLikeCredential = looksLikeCredential;
28
+ exports.assertNoCredentialField = assertNoCredentialField;
29
+ /**
30
+ * The noun a credential field ends with.
31
+ *
32
+ * **The last word, not any word.** `apiToken` is a token; `tokenCount` is a number of them and
33
+ * `tokenizer` is neither. Matching anywhere in the name refuses all three, and refusing a
34
+ * legitimate field is not free — the author is blocked with nowhere to go.
35
+ */
36
+ const CREDENTIAL_NOUNS = ['secret', 'token', 'password', 'passwd', 'credential', 'passphrase'];
37
+ /**
38
+ * Two words that only mean a credential together.
39
+ *
40
+ * `key` alone is far too common (`orderKey`, `groupKey`), so it is only read as a secret when the
41
+ * word before it says which kind.
42
+ */
43
+ const CREDENTIAL_PAIRS = ['apikey', 'accesskey', 'privatekey', 'secretkey', 'sshkey', 'signingkey'];
44
+ /**
45
+ * Split a field name into lowercase words, across camelCase, snake_case, kebab and dots.
46
+ *
47
+ * Segments rather than substrings, so the rule can look at where the word sits rather than merely
48
+ * whether it appears.
49
+ */
50
+ function wordsOf(name) {
51
+ return String(name || '')
52
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
53
+ .split(/[^a-zA-Z0-9]+/)
54
+ .map(word => word.toLowerCase())
55
+ .filter(Boolean);
56
+ }
57
+ /** Does this field name say it holds a credential? */
58
+ function looksLikeCredential(name) {
59
+ const words = wordsOf(name);
60
+ if (!words.length) {
61
+ return false;
62
+ }
63
+ const last = words[words.length - 1];
64
+ if (CREDENTIAL_NOUNS.includes(last)) {
65
+ return true;
66
+ }
67
+ const pair = words.length > 1 ? words[words.length - 2] + last : '';
68
+ return CREDENTIAL_PAIRS.includes(pair);
69
+ }
70
+ /**
71
+ * Refuse a model that declares a credential field, and say what to do about it.
72
+ *
73
+ * The message names the field, because a model can carry many and "one of them" would send the
74
+ * author looking.
75
+ */
76
+ function assertNoCredentialField(model, activityName) {
77
+ const offenders = (model || []).map(item => item?.name).filter(name => name && looksLikeCredential(name));
78
+ if (!offenders.length) {
79
+ return;
80
+ }
81
+ throw new Error(`[activity] ${activityName ? `"${activityName}" ` : ''}declares ${offenders.length > 1 ? 'fields' : 'a field'} ` +
82
+ `that would hold a credential: ${offenders.join(', ')}. ` +
83
+ 'Worklist stores an activity input as plain JSON, shows it on the approval screen, lets it be ' +
84
+ 'copied, and keeps a copy in two history tables — a secret put there is readable in four ' +
85
+ 'places. Sealing secret values is designed but not built (secret flag + value-level sealing + ' +
86
+ 'display, to land together at first real use). Raise it to the architect lane rather than ' +
87
+ 'working around this.');
88
+ }
89
+ //# sourceMappingURL=credential-field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-field.js","sourceRoot":"","sources":["../../../server/service/activity/credential-field.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;;AAkCH,kDAgBC;AAQD,0DAgBC;AAxED;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,CAAC,CAAA;AAE9F;;;;;GAKG;AACH,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAA;AAEnG;;;;;GAKG;AACH,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;SACtB,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;SACtC,KAAK,CAAC,eAAe,CAAC;SACtB,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;SAC/B,MAAM,CAAC,OAAO,CAAC,CAAA;AACpB,CAAC;AAED,sDAAsD;AACtD,SAAgB,mBAAmB,CAAC,IAAY;IAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3B,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAEpC,IAAI,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;IAEnE,OAAO,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;AACxC,CAAC;AAED;;;;;GAKG;AACH,SAAgB,uBAAuB,CAAC,KAAsC,EAAE,YAAqB;IACnG,MAAM,SAAS,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAA;IAEzG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;QACtB,OAAM;IACR,CAAC;IAED,MAAM,IAAI,KAAK,CACb,cAAc,YAAY,CAAC,CAAC,CAAC,IAAI,YAAY,IAAI,CAAC,CAAC,CAAC,EAAE,YAAY,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,GAAG;QAC9G,iCAAiC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;QACzD,+FAA+F;QAC/F,0FAA0F;QAC1F,+FAA+F;QAC/F,2FAA2F;QAC3F,sBAAsB,CACzB,CAAA;AACH,CAAC","sourcesContent":["/**\n * **This framework cannot hold a credential yet, so it refuses to be handed one.**\n *\n * An activity's model decides what goes into `ActivityInstance.input`, and that column is\n * `simple-json` in the clear. There is no way to mark a field as secret, nothing masks it on the\n * approval screen, the payload copy button copies whatever is there, and the two history entities\n * keep their own copy of the same value. A token entered today would be readable in four places.\n *\n * Nothing declares one right now — every registered activity across both repos was checked and\n * none has a credential-shaped field. So this is a door being closed while it costs nothing, not\n * a leak being patched.\n *\n * ── Why refuse instead of building the sealing (2026-09-09) ───────────────\n * The architect lane ruled the shape: a `secret?: boolean` flag on the model item (not a new\n * ActivityModelItemType — \"how is this typed in\" and \"is this a secret\" are different axes, and a\n * renderer missing the new case would fall through `default` and print the value), sealing at the\n * value level rather than the column (the column is filtered on, and sealing it whole would kill\n * that), and the stored shape travelling outward unchanged so there is nothing left to mask.\n *\n * Built now, that machinery would harden with no one using it. Built at first real use, all three\n * parts land together. Until then this refusal keeps the gap from being walked into.\n *\n * It has to run at save time, not in a test: models are authored on screen, so no test sees them.\n */\n\n/**\n * The noun a credential field ends with.\n *\n * **The last word, not any word.** `apiToken` is a token; `tokenCount` is a number of them and\n * `tokenizer` is neither. Matching anywhere in the name refuses all three, and refusing a\n * legitimate field is not free — the author is blocked with nowhere to go.\n */\nconst CREDENTIAL_NOUNS = ['secret', 'token', 'password', 'passwd', 'credential', 'passphrase']\n\n/**\n * Two words that only mean a credential together.\n *\n * `key` alone is far too common (`orderKey`, `groupKey`), so it is only read as a secret when the\n * word before it says which kind.\n */\nconst CREDENTIAL_PAIRS = ['apikey', 'accesskey', 'privatekey', 'secretkey', 'sshkey', 'signingkey']\n\n/**\n * Split a field name into lowercase words, across camelCase, snake_case, kebab and dots.\n *\n * Segments rather than substrings, so the rule can look at where the word sits rather than merely\n * whether it appears.\n */\nfunction wordsOf(name: string): string[] {\n return String(name || '')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .split(/[^a-zA-Z0-9]+/)\n .map(word => word.toLowerCase())\n .filter(Boolean)\n}\n\n/** Does this field name say it holds a credential? */\nexport function looksLikeCredential(name: string): boolean {\n const words = wordsOf(name)\n\n if (!words.length) {\n return false\n }\n\n const last = words[words.length - 1]\n\n if (CREDENTIAL_NOUNS.includes(last)) {\n return true\n }\n\n const pair = words.length > 1 ? words[words.length - 2] + last : ''\n\n return CREDENTIAL_PAIRS.includes(pair)\n}\n\n/**\n * Refuse a model that declares a credential field, and say what to do about it.\n *\n * The message names the field, because a model can carry many and \"one of them\" would send the\n * author looking.\n */\nexport function assertNoCredentialField(model: { name?: string }[] | undefined, activityName?: string): void {\n const offenders = (model || []).map(item => item?.name).filter(name => name && looksLikeCredential(name))\n\n if (!offenders.length) {\n return\n }\n\n throw new Error(\n `[activity] ${activityName ? `\"${activityName}\" ` : ''}declares ${offenders.length > 1 ? 'fields' : 'a field'} ` +\n `that would hold a credential: ${offenders.join(', ')}. ` +\n 'Worklist stores an activity input as plain JSON, shows it on the approval screen, lets it be ' +\n 'copied, and keeps a copy in two history tables — a secret put there is readable in four ' +\n 'places. Sealing secret values is designed but not built (secret flag + value-level sealing + ' +\n 'display, to land together at first real use). Raise it to the architect lane rather than ' +\n 'working around this.'\n )\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { EntitySubscriberInterface, InsertEvent, UpdateEvent } from 'typeorm';
2
+ import { Activity } from './activity.js';
3
+ /**
4
+ * **One place, because there are five ways in.**
5
+ *
6
+ * Activities are saved by createActivity, updateActivity, multipleUpsertActivities, the importer,
7
+ * and installActivity. A check written at each of those is not a rule — it is five things to
8
+ * remember, and the sixth way in arrives without it.
9
+ *
10
+ * TypeORM's subscriber is the seam every one of them passes through. Throwing here aborts the
11
+ * transaction, so a refused model is never half-written.
12
+ *
13
+ * A template carrying such a field is caught here too: installActivity saves an Activity from it.
14
+ */
15
+ export declare class ActivityCredentialGuardSubscriber implements EntitySubscriberInterface<Activity> {
16
+ listenTo(): typeof Activity;
17
+ beforeInsert(event: InsertEvent<Activity>): void;
18
+ beforeUpdate(event: UpdateEvent<Activity>): void;
19
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ActivityCredentialGuardSubscriber = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const typeorm_1 = require("typeorm");
6
+ const activity_js_1 = require("./activity.js");
7
+ const credential_field_js_1 = require("./credential-field.js");
8
+ /**
9
+ * **One place, because there are five ways in.**
10
+ *
11
+ * Activities are saved by createActivity, updateActivity, multipleUpsertActivities, the importer,
12
+ * and installActivity. A check written at each of those is not a rule — it is five things to
13
+ * remember, and the sixth way in arrives without it.
14
+ *
15
+ * TypeORM's subscriber is the seam every one of them passes through. Throwing here aborts the
16
+ * transaction, so a refused model is never half-written.
17
+ *
18
+ * A template carrying such a field is caught here too: installActivity saves an Activity from it.
19
+ */
20
+ let ActivityCredentialGuardSubscriber = class ActivityCredentialGuardSubscriber {
21
+ listenTo() {
22
+ return activity_js_1.Activity;
23
+ }
24
+ beforeInsert(event) {
25
+ (0, credential_field_js_1.assertNoCredentialField)(event.entity?.model, event.entity?.name);
26
+ }
27
+ beforeUpdate(event) {
28
+ (0, credential_field_js_1.assertNoCredentialField)(event.entity?.model, event.entity?.name);
29
+ }
30
+ };
31
+ exports.ActivityCredentialGuardSubscriber = ActivityCredentialGuardSubscriber;
32
+ exports.ActivityCredentialGuardSubscriber = ActivityCredentialGuardSubscriber = tslib_1.__decorate([
33
+ (0, typeorm_1.EventSubscriber)()
34
+ ], ActivityCredentialGuardSubscriber);
35
+ //# sourceMappingURL=credential-guard-subscriber.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-guard-subscriber.js","sourceRoot":"","sources":["../../../server/service/activity/credential-guard-subscriber.ts"],"names":[],"mappings":";;;;AAAA,qCAA8F;AAE9F,+CAAwC;AACxC,+DAA+D;AAE/D;;;;;;;;;;;GAWG;AAEI,IAAM,iCAAiC,GAAvC,MAAM,iCAAiC;IAC5C,QAAQ;QACN,OAAO,sBAAQ,CAAA;IACjB,CAAC;IAED,YAAY,CAAC,KAA4B;QACvC,IAAA,6CAAuB,EAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAClE,CAAC;IAED,YAAY,CAAC,KAA4B;QACvC,IAAA,6CAAuB,EAAE,KAAK,CAAC,MAAmB,EAAE,KAAK,EAAG,KAAK,CAAC,MAAmB,EAAE,IAAI,CAAC,CAAA;IAC9F,CAAC;CACF,CAAA;AAZY,8EAAiC;4CAAjC,iCAAiC;IAD7C,IAAA,yBAAe,GAAE;GACL,iCAAiC,CAY7C","sourcesContent":["import { EntitySubscriberInterface, EventSubscriber, InsertEvent, UpdateEvent } from 'typeorm'\n\nimport { Activity } from './activity.js'\nimport { assertNoCredentialField } from './credential-field.js'\n\n/**\n * **One place, because there are five ways in.**\n *\n * Activities are saved by createActivity, updateActivity, multipleUpsertActivities, the importer,\n * and installActivity. A check written at each of those is not a rule — it is five things to\n * remember, and the sixth way in arrives without it.\n *\n * TypeORM's subscriber is the seam every one of them passes through. Throwing here aborts the\n * transaction, so a refused model is never half-written.\n *\n * A template carrying such a field is caught here too: installActivity saves an Activity from it.\n */\n@EventSubscriber()\nexport class ActivityCredentialGuardSubscriber implements EntitySubscriberInterface<Activity> {\n listenTo() {\n return Activity\n }\n\n beforeInsert(event: InsertEvent<Activity>) {\n assertNoCredentialField(event.entity?.model, event.entity?.name)\n }\n\n beforeUpdate(event: UpdateEvent<Activity>) {\n assertNoCredentialField((event.entity as Activity)?.model, (event.entity as Activity)?.name)\n }\n}\n"]}
@@ -3,6 +3,7 @@ import { ActivityHistory } from "./activity-history.js";
3
3
  import { ActivityMutation } from "./activity-mutation.js";
4
4
  import { ActivityQuery } from "./activity-query.js";
5
5
  import { ActivityHistoryEntitySubscriber } from "./event-subscriber.js";
6
+ import { ActivityCredentialGuardSubscriber } from "./credential-guard-subscriber.js";
6
7
  export declare const entities: (typeof Activity | typeof ActivityHistory)[];
7
8
  export declare const resolvers: (typeof ActivityMutation | typeof ActivityQuery)[];
8
- export declare const subscribers: (typeof ActivityHistoryEntitySubscriber)[];
9
+ export declare const subscribers: (typeof ActivityHistoryEntitySubscriber | typeof ActivityCredentialGuardSubscriber)[];
@@ -6,7 +6,8 @@ const activity_history_js_1 = require("./activity-history.js");
6
6
  const activity_mutation_js_1 = require("./activity-mutation.js");
7
7
  const activity_query_js_1 = require("./activity-query.js");
8
8
  const event_subscriber_js_1 = require("./event-subscriber.js");
9
+ const credential_guard_subscriber_js_1 = require("./credential-guard-subscriber.js");
9
10
  exports.entities = [activity_js_1.Activity, activity_history_js_1.ActivityHistory];
10
11
  exports.resolvers = [activity_query_js_1.ActivityQuery, activity_mutation_js_1.ActivityMutation];
11
- exports.subscribers = [event_subscriber_js_1.ActivityHistoryEntitySubscriber];
12
+ exports.subscribers = [event_subscriber_js_1.ActivityHistoryEntitySubscriber, credential_guard_subscriber_js_1.ActivityCredentialGuardSubscriber];
12
13
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/activity/index.ts"],"names":[],"mappings":";;;AAAA,+CAAwC;AACxC,+DAAuD;AACvD,iEAAyD;AACzD,2DAAmD;AACnD,+DAAuE;AAE1D,QAAA,QAAQ,GAAG,CAAC,sBAAQ,EAAE,qCAAe,CAAC,CAAA;AACtC,QAAA,SAAS,GAAG,CAAC,iCAAa,EAAE,uCAAgB,CAAC,CAAA;AAC7C,QAAA,WAAW,GAAG,CAAC,qDAA+B,CAAC,CAAA","sourcesContent":["import { Activity } from \"./activity.js\"\nimport { ActivityHistory } from \"./activity-history.js\"\nimport { ActivityMutation } from \"./activity-mutation.js\"\nimport { ActivityQuery } from \"./activity-query.js\"\nimport { ActivityHistoryEntitySubscriber } from \"./event-subscriber.js\"\n\nexport const entities = [Activity, ActivityHistory]\nexport const resolvers = [ActivityQuery, ActivityMutation]\nexport const subscribers = [ActivityHistoryEntitySubscriber]\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/activity/index.ts"],"names":[],"mappings":";;;AAAA,+CAAwC;AACxC,+DAAuD;AACvD,iEAAyD;AACzD,2DAAmD;AACnD,+DAAuE;AACvE,qFAAoF;AAEvE,QAAA,QAAQ,GAAG,CAAC,sBAAQ,EAAE,qCAAe,CAAC,CAAA;AACtC,QAAA,SAAS,GAAG,CAAC,iCAAa,EAAE,uCAAgB,CAAC,CAAA;AAC7C,QAAA,WAAW,GAAG,CAAC,qDAA+B,EAAE,kEAAiC,CAAC,CAAA","sourcesContent":["import { Activity } from \"./activity.js\"\nimport { ActivityHistory } from \"./activity-history.js\"\nimport { ActivityMutation } from \"./activity-mutation.js\"\nimport { ActivityQuery } from \"./activity-query.js\"\nimport { ActivityHistoryEntitySubscriber } from \"./event-subscriber.js\"\nimport { ActivityCredentialGuardSubscriber } from \"./credential-guard-subscriber.js\"\n\nexport const entities = [Activity, ActivityHistory]\nexport const resolvers = [ActivityQuery, ActivityMutation]\nexport const subscribers = [ActivityHistoryEntitySubscriber, ActivityCredentialGuardSubscriber]\n"]}
@@ -6,7 +6,7 @@ export * from './activity/activity.js';
6
6
  export * from './installable-activity/installable-activity.js';
7
7
  export * from './activity-summary/activity-summary.js';
8
8
  export declare const entities: (typeof import("./activity/activity.js").Activity | typeof import("./activity-approval/activity-approval.js").ActivityApproval | typeof import("./activity-thread/activity-thread.js").ActivityThread | typeof import("./activity-instance/activity-instance.js").ActivityInstance | typeof import("./installable-activity/installable-activity.js").InstallableActivity | typeof import("./activity-template/activity-template.js").ActivityTemplate | typeof import("./activity-summary/activity-summary.js").ActivitySummary | typeof import("./activity/activity-history.js").ActivityHistory | typeof import("./activity-instance/activity-instance-history.js").ActivityInstanceHistory | typeof import("./activity-template/activity-template-history.js").ActivityTemplateHistory | typeof import("./activity-thread/activity-thread-history.js").ActivityThreadHistory)[];
9
- export declare const subscribers: (typeof import("./activity-approval/event-subscriber.js").ActivityApprovalSubscriber | typeof import("./activity/event-subscriber.js").ActivityHistoryEntitySubscriber | typeof import("./activity-instance/event-subscriber.js").ActivityInstanceSubscriber | typeof import("./activity-instance/event-subscriber.js").ActivityInstanceHistoryEntitySubscriber | typeof import("./activity-thread/event-subscriber.js").ActivityThreadSubscriber | typeof import("./activity-thread/event-subscriber.js").ActivityThreadHistoryEntitySubscriber)[];
9
+ export declare const subscribers: (typeof import("./activity-approval/event-subscriber.js").ActivityApprovalSubscriber | typeof import("./activity/event-subscriber.js").ActivityHistoryEntitySubscriber | typeof import("./activity/credential-guard-subscriber.js").ActivityCredentialGuardSubscriber | typeof import("./activity-instance/event-subscriber.js").ActivityInstanceSubscriber | typeof import("./activity-instance/event-subscriber.js").ActivityInstanceHistoryEntitySubscriber | typeof import("./activity-thread/event-subscriber.js").ActivityThreadSubscriber | typeof import("./activity-thread/event-subscriber.js").ActivityThreadHistoryEntitySubscriber)[];
10
10
  export declare const schema: {
11
11
  resolverClasses: (typeof import("./activity-stats/activity-stats-query.js").ActivityStatsQuery | typeof import("./activity-approval/activity-approval-query.js").ActivityApprovalQuery | typeof import("./activity-approval/activity-approval-mutation.js").ActivityApprovalMutation | typeof import("./activity-approval/activity-approval-subscription.js").ActivityApprovalSubscription | typeof import("./activity/activity-mutation.js").ActivityMutation | typeof import("./activity/activity-query.js").ActivityQuery | typeof import("./activity-instance/activity-instance-mutation.js").ActivityInstanceMutation | typeof import("./activity-instance/activity-instance-query.js").ActivityInstanceQuery | typeof import("./activity-instance/activity-instance-history-query.js").ActivityInstanceHistoryQuery | typeof import("./activity-instance/activity-instance-subscription.js").ActivityInstanceSubscription | typeof import("./activity-template/activity-template-mutation.js").ActivityTemplateMutation | typeof import("./activity-template/activity-template-query.js").ActivityTemplateQuery | typeof import("./activity-thread/activity-thread-mutation.js").ActivityThreadMutation | typeof import("./activity-thread/activity-thread-query.js").ActivityThreadQuery | typeof import("./activity-thread/activity-thread-history-query.js").ActivityThreadHistoryQuery | typeof import("./activity-thread/activity-thread-subscription.js").ActivityThreadSubscription | typeof import("./installable-activity/installable-activity-query.js").InstallableActivityQuery | typeof import("./installable-activity/installable-activity-mutation.js").InstallableActivityMutation | typeof import("./activity-summary/activity-summary-query.js").ActivitySummaryQuery)[];
12
12
  };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * How far along an activity type is, **as declared by the module that registers it.**
3
+ *
4
+ * Nobody can compute this at runtime. A module either works end to end or it does not, and only
5
+ * the people who wrote it know which. So it is declared beside the callback, the way plant declares
6
+ * its maturity facts — and like those, it can go stale, which is why the declaration also names
7
+ * the tests that back it up.
8
+ */
9
+ export declare const ActivityReadinessStage: {
10
+ /** Registered, and the callback does the real thing. */
11
+ readonly Working: "working";
12
+ /** Registered and reachable, but part of the effect is still missing. Say what in `gaps`. */
13
+ readonly Partial: "partial";
14
+ /** The type exists so the screen can show it. Issuing one does not accomplish anything yet. */
15
+ readonly Declared: "declared";
16
+ /**
17
+ * Identified as work a person has to decide, and **not built.** Often the model has no place to
18
+ * put it yet — say what is missing in `gaps`.
19
+ *
20
+ * The catalog carries these because it is the plan of record: a reader asking "what is left"
21
+ * gets the answer on the screen instead of in someone's head.
22
+ */
23
+ readonly Planned: "planned";
24
+ /**
25
+ * Looked at and **deliberately not a worklist item.** This is a placement decision, not a
26
+ * backlog entry — the work happens, it just does not reach anyone's inbox. Fact-derived things
27
+ * live like this: a machine stops and the reason gets filled in at the machine, because nobody
28
+ * assigned it.
29
+ *
30
+ * It sits in the catalog so the next person asking "why is this not here" reads the answer
31
+ * rather than proposing it again. `notes` says where it does live.
32
+ *
33
+ * Kept apart from Planned on purpose. Mixed together, a decision reads as a promise.
34
+ */
35
+ readonly NotWorklist: "not-worklist";
36
+ };
37
+ export type ActivityReadinessStage = (typeof ActivityReadinessStage)[keyof typeof ActivityReadinessStage];
38
+ export declare class ActivityTestRecord {
39
+ passed?: number;
40
+ total?: number;
41
+ at?: string;
42
+ command?: string;
43
+ }
44
+ /**
45
+ * What a module says about its own activity type — how far along, and what backs that up.
46
+ *
47
+ * **Every field is optional and a missing one stays missing.** A module that declares nothing gets
48
+ * a row saying 「선언 없음」, never a zero and never a green mark. That distinction is the whole
49
+ * point of the screen: a type nobody has vouched for should look different from one that is
50
+ * finished, and a count we do not have is not a count of zero.
51
+ */
52
+ export declare class ActivityReadiness {
53
+ stage?: ActivityReadinessStage;
54
+ gaps?: string[];
55
+ tests?: string[];
56
+ lastRun?: ActivityTestRecord;
57
+ notes?: string;
58
+ }
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ActivityReadiness = exports.ActivityTestRecord = exports.ActivityReadinessStage = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ /**
7
+ * How far along an activity type is, **as declared by the module that registers it.**
8
+ *
9
+ * Nobody can compute this at runtime. A module either works end to end or it does not, and only
10
+ * the people who wrote it know which. So it is declared beside the callback, the way plant declares
11
+ * its maturity facts — and like those, it can go stale, which is why the declaration also names
12
+ * the tests that back it up.
13
+ */
14
+ exports.ActivityReadinessStage = {
15
+ /** Registered, and the callback does the real thing. */
16
+ Working: 'working',
17
+ /** Registered and reachable, but part of the effect is still missing. Say what in `gaps`. */
18
+ Partial: 'partial',
19
+ /** The type exists so the screen can show it. Issuing one does not accomplish anything yet. */
20
+ Declared: 'declared',
21
+ /**
22
+ * Identified as work a person has to decide, and **not built.** Often the model has no place to
23
+ * put it yet — say what is missing in `gaps`.
24
+ *
25
+ * The catalog carries these because it is the plan of record: a reader asking "what is left"
26
+ * gets the answer on the screen instead of in someone's head.
27
+ */
28
+ Planned: 'planned',
29
+ /**
30
+ * Looked at and **deliberately not a worklist item.** This is a placement decision, not a
31
+ * backlog entry — the work happens, it just does not reach anyone's inbox. Fact-derived things
32
+ * live like this: a machine stops and the reason gets filled in at the machine, because nobody
33
+ * assigned it.
34
+ *
35
+ * It sits in the catalog so the next person asking "why is this not here" reads the answer
36
+ * rather than proposing it again. `notes` says where it does live.
37
+ *
38
+ * Kept apart from Planned on purpose. Mixed together, a decision reads as a promise.
39
+ */
40
+ NotWorklist: 'not-worklist'
41
+ };
42
+ (0, type_graphql_1.registerEnumType)(exports.ActivityReadinessStage, {
43
+ name: 'ActivityReadinessStage',
44
+ description: 'how far along an installable activity is, as declared by the module that registers it'
45
+ });
46
+ let ActivityTestRecord = class ActivityTestRecord {
47
+ };
48
+ exports.ActivityTestRecord = ActivityTestRecord;
49
+ tslib_1.__decorate([
50
+ (0, type_graphql_1.Field)({ nullable: true, description: 'How many of the covering tests passed' }),
51
+ tslib_1.__metadata("design:type", Number)
52
+ ], ActivityTestRecord.prototype, "passed", void 0);
53
+ tslib_1.__decorate([
54
+ (0, type_graphql_1.Field)({ nullable: true, description: 'How many covering tests ran' }),
55
+ tslib_1.__metadata("design:type", Number)
56
+ ], ActivityTestRecord.prototype, "total", void 0);
57
+ tslib_1.__decorate([
58
+ (0, type_graphql_1.Field)({ nullable: true, description: 'When that run happened, ISO 8601' }),
59
+ tslib_1.__metadata("design:type", String)
60
+ ], ActivityTestRecord.prototype, "at", void 0);
61
+ tslib_1.__decorate([
62
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The command that produced this record' }),
63
+ tslib_1.__metadata("design:type", String)
64
+ ], ActivityTestRecord.prototype, "command", void 0);
65
+ exports.ActivityTestRecord = ActivityTestRecord = tslib_1.__decorate([
66
+ (0, type_graphql_1.ObjectType)({ description: 'Recorded result of the test run that covers an installable activity' })
67
+ ], ActivityTestRecord);
68
+ /**
69
+ * What a module says about its own activity type — how far along, and what backs that up.
70
+ *
71
+ * **Every field is optional and a missing one stays missing.** A module that declares nothing gets
72
+ * a row saying 「선언 없음」, never a zero and never a green mark. That distinction is the whole
73
+ * point of the screen: a type nobody has vouched for should look different from one that is
74
+ * finished, and a count we do not have is not a count of zero.
75
+ */
76
+ let ActivityReadiness = class ActivityReadiness {
77
+ };
78
+ exports.ActivityReadiness = ActivityReadiness;
79
+ tslib_1.__decorate([
80
+ (0, type_graphql_1.Field)(type => exports.ActivityReadinessStage, { nullable: true, description: 'Working, partial, or declared only' }),
81
+ tslib_1.__metadata("design:type", String)
82
+ ], ActivityReadiness.prototype, "stage", void 0);
83
+ tslib_1.__decorate([
84
+ (0, type_graphql_1.Field)(type => [String], { nullable: true, description: 'What is still missing, when the stage is partial' }),
85
+ tslib_1.__metadata("design:type", Array)
86
+ ], ActivityReadiness.prototype, "gaps", void 0);
87
+ tslib_1.__decorate([
88
+ (0, type_graphql_1.Field)(type => [String], {
89
+ nullable: true,
90
+ description: 'Repo-relative test files that cover this activity. Named so the claim can be checked.'
91
+ }),
92
+ tslib_1.__metadata("design:type", Array)
93
+ ], ActivityReadiness.prototype, "tests", void 0);
94
+ tslib_1.__decorate([
95
+ (0, type_graphql_1.Field)(type => ActivityTestRecord, { nullable: true, description: 'Result of the last recorded run of those tests' }),
96
+ tslib_1.__metadata("design:type", ActivityTestRecord)
97
+ ], ActivityReadiness.prototype, "lastRun", void 0);
98
+ tslib_1.__decorate([
99
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Anything a reader needs that the fields above do not carry' }),
100
+ tslib_1.__metadata("design:type", String)
101
+ ], ActivityReadiness.prototype, "notes", void 0);
102
+ exports.ActivityReadiness = ActivityReadiness = tslib_1.__decorate([
103
+ (0, type_graphql_1.ObjectType)({ description: 'What the registering module declares about an installable activity' })
104
+ ], ActivityReadiness);
105
+ //# sourceMappingURL=activity-readiness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-readiness.js","sourceRoot":"","sources":["../../../server/service/installable-activity/activity-readiness.ts"],"names":[],"mappings":";;;;AAAA,+CAAkE;AAElE;;;;;;;GAOG;AACU,QAAA,sBAAsB,GAAG;IACpC,wDAAwD;IACxD,OAAO,EAAE,SAAS;IAClB,6FAA6F;IAC7F,OAAO,EAAE,SAAS;IAClB,+FAA+F;IAC/F,QAAQ,EAAE,UAAU;IACpB;;;;;;OAMG;IACH,OAAO,EAAE,SAAS;IAClB;;;;;;;;;;OAUG;IACH,WAAW,EAAE,cAAc;CACnB,CAAA;AAIV,IAAA,+BAAgB,EAAC,8BAAsB,EAAE;IACvC,IAAI,EAAE,wBAAwB;IAC9B,WAAW,EAAE,uFAAuF;CACrG,CAAC,CAAA;AAGK,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;CAY9B,CAAA;AAZY,gDAAkB;AAE7B;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uCAAuC,EAAE,CAAC;;kDACjE;AAGf;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,6BAA6B,EAAE,CAAC;;iDACxD;AAGd;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kCAAkC,EAAE,CAAC;;8CAChE;AAGX;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uCAAuC,EAAE,CAAC;;mDAChE;6BAXL,kBAAkB;IAD9B,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,qEAAqE,EAAE,CAAC;GACtF,kBAAkB,CAY9B;AAED;;;;;;;GAOG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;CAkB7B,CAAA;AAlBY,8CAAiB;AAE5B;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,8BAAsB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,oCAAoC,EAAE,CAAC;;gDAC/E;AAG9B;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kDAAkD,EAAE,CAAC;;+CAC9F;AAMf;IAJC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE;QACvB,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,uFAAuF;KACrG,CAAC;;gDACc;AAGhB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAkB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,gDAAgD,EAAE,CAAC;sCAC3G,kBAAkB;kDAAA;AAG5B;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,4DAA4D,EAAE,CAAC;;gDACvF;4BAjBH,iBAAiB;IAD7B,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,oEAAoE,EAAE,CAAC;GACrF,iBAAiB,CAkB7B","sourcesContent":["import { Field, ObjectType, registerEnumType } from 'type-graphql'\n\n/**\n * How far along an activity type is, **as declared by the module that registers it.**\n *\n * Nobody can compute this at runtime. A module either works end to end or it does not, and only\n * the people who wrote it know which. So it is declared beside the callback, the way plant declares\n * its maturity facts — and like those, it can go stale, which is why the declaration also names\n * the tests that back it up.\n */\nexport const ActivityReadinessStage = {\n /** Registered, and the callback does the real thing. */\n Working: 'working',\n /** Registered and reachable, but part of the effect is still missing. Say what in `gaps`. */\n Partial: 'partial',\n /** The type exists so the screen can show it. Issuing one does not accomplish anything yet. */\n Declared: 'declared',\n /**\n * Identified as work a person has to decide, and **not built.** Often the model has no place to\n * put it yet — say what is missing in `gaps`.\n *\n * The catalog carries these because it is the plan of record: a reader asking \"what is left\"\n * gets the answer on the screen instead of in someone's head.\n */\n Planned: 'planned',\n /**\n * Looked at and **deliberately not a worklist item.** This is a placement decision, not a\n * backlog entry — the work happens, it just does not reach anyone's inbox. Fact-derived things\n * live like this: a machine stops and the reason gets filled in at the machine, because nobody\n * assigned it.\n *\n * It sits in the catalog so the next person asking \"why is this not here\" reads the answer\n * rather than proposing it again. `notes` says where it does live.\n *\n * Kept apart from Planned on purpose. Mixed together, a decision reads as a promise.\n */\n NotWorklist: 'not-worklist'\n} as const\n\nexport type ActivityReadinessStage = (typeof ActivityReadinessStage)[keyof typeof ActivityReadinessStage]\n\nregisterEnumType(ActivityReadinessStage, {\n name: 'ActivityReadinessStage',\n description: 'how far along an installable activity is, as declared by the module that registers it'\n})\n\n@ObjectType({ description: 'Recorded result of the test run that covers an installable activity' })\nexport class ActivityTestRecord {\n @Field({ nullable: true, description: 'How many of the covering tests passed' })\n passed?: number\n\n @Field({ nullable: true, description: 'How many covering tests ran' })\n total?: number\n\n @Field({ nullable: true, description: 'When that run happened, ISO 8601' })\n at?: string\n\n @Field({ nullable: true, description: 'The command that produced this record' })\n command?: string\n}\n\n/**\n * What a module says about its own activity type — how far along, and what backs that up.\n *\n * **Every field is optional and a missing one stays missing.** A module that declares nothing gets\n * a row saying 「선언 없음」, never a zero and never a green mark. That distinction is the whole\n * point of the screen: a type nobody has vouched for should look different from one that is\n * finished, and a count we do not have is not a count of zero.\n */\n@ObjectType({ description: 'What the registering module declares about an installable activity' })\nexport class ActivityReadiness {\n @Field(type => ActivityReadinessStage, { nullable: true, description: 'Working, partial, or declared only' })\n stage?: ActivityReadinessStage\n\n @Field(type => [String], { nullable: true, description: 'What is still missing, when the stage is partial' })\n gaps?: string[]\n\n @Field(type => [String], {\n nullable: true,\n description: 'Repo-relative test files that cover this activity. Named so the claim can be checked.'\n })\n tests?: string[]\n\n @Field(type => ActivityTestRecord, { nullable: true, description: 'Result of the last recorded run of those tests' })\n lastRun?: ActivityTestRecord\n\n @Field({ nullable: true, description: 'Anything a reader needs that the fields above do not carry' })\n notes?: string\n}\n"]}
@@ -7,6 +7,7 @@ const shell_1 = require("@things-factory/shell");
7
7
  const activity_installation_controller_js_1 = require("../../controllers/activity-installation/activity-installation-controller.js");
8
8
  const activity_js_1 = require("../activity/activity.js");
9
9
  const installable_activity_js_1 = require("./installable-activity.js");
10
+ const approval_line_js_1 = require("../../controllers/activity-approval/approval-line.js");
10
11
  const installable_activity_type_js_1 = require("./installable-activity-type.js");
11
12
  let InstallableActivityQuery = class InstallableActivityQuery {
12
13
  async installableActivity(name, context) {
@@ -20,9 +21,33 @@ let InstallableActivityQuery = class InstallableActivityQuery {
20
21
  domain: { id: domain.id },
21
22
  name: item.name
22
23
  });
24
+ /*
25
+ * 「이 도메인이 이 단계를 쓰나」를 값으로 낸다 — 판단은 여기서 다시 짜지 않는다.
26
+ *
27
+ * `isApprovalStep` 이 참조를 걸러 내는 그 자리다. 화면이 길이를 세면 참조만 있는 결재선을
28
+ * 「결재자 있음」으로 그리고, 실제로는 그냥 지나간다.
29
+ */
30
+ item.approvalSteps = (item.activity?.approvalLine || []).filter(approval_line_js_1.isApprovalStep).length;
23
31
  }
24
- const total = items.length;
25
- return { items, total };
32
+ /*
33
+ * Identified-but-unbuilt work comes out of this query too, because the catalog is the plan of
34
+ * record — "what is left" has to be answerable on the screen.
35
+ *
36
+ * Shaped to look like an installable activity so the screen has one list to draw, but it
37
+ * carries no `activity` (nothing is installed) and no callback. Its stage says planned or
38
+ * not-worklist, and the screen keeps those apart from the built ones: a decision must not read
39
+ * as a promise.
40
+ */
41
+ const intended = activity_installation_controller_js_1.ActivityInstallations.listIntended().map(one => ({
42
+ name: one.key,
43
+ label: one.label,
44
+ description: undefined,
45
+ guide: one.guide,
46
+ category: one.category,
47
+ readiness: one.readiness
48
+ }));
49
+ const merged = [...items, ...intended];
50
+ return { items: merged, total: merged.length };
26
51
  }
27
52
  };
28
53
  exports.InstallableActivityQuery = InstallableActivityQuery;