dna-sdk 0.11.0 β†’ 0.13.0

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.
package/dist/bootstrap.js CHANGED
@@ -42,6 +42,7 @@ import { CloudExtension } from "./extensions/cloud.js";
42
42
  import { AutomationExtension } from "./extensions/automation.js";
43
43
  import { EvalExtension } from "./extensions/eval.js";
44
44
  import { IntelExtension } from "./extensions/intel.js";
45
+ import { PortfolioExtension } from "./extensions/portfolio.js";
45
46
  import { loadConfig } from "./config.js";
46
47
  import { resolveDefaultFsUrl, sourceFromUrl } from "./adapters/source-url.js";
47
48
  /**
@@ -75,6 +76,7 @@ function loadBuiltins(target) {
75
76
  new AutomationExtension(),
76
77
  new EvalExtension(),
77
78
  new IntelExtension(),
79
+ new PortfolioExtension(),
78
80
  ]) {
79
81
  target.load(ext);
80
82
  }
@@ -21,6 +21,10 @@ class GuardrailKind extends KindBase {
21
21
  promptTargetPriority = 0;
22
22
  flattenInContext = false;
23
23
  descriptionFallbackField = "instruction";
24
+ // Validate on read/compose, not only on write (i-validation-shallow) β€” parity
25
+ // with Python GuardrailKind.validate_on_parse. parse() below already throws on
26
+ // a bad severity/scope via GuardrailSchema (z.enum); this declares the intent.
27
+ validateOnParse = true;
24
28
  storage = SD.bundle("guardrails", "GUARDRAIL.md", "list", "rules");
25
29
  graphStyle = { fill: "#EF4444", stroke: "#DC2626", textColor: "#fff" };
26
30
  asciiIcon = "πŸ›‘οΈ";
@@ -30,11 +30,23 @@ const GUARDRAILS_BLOCK = "{{#guardrails-guardrail}}" +
30
30
  "{{#description}}_{{description}}_\n\n{{/description}}" +
31
31
  "{{#rules}}- {{{.}}}\n{{/rules}}\n" +
32
32
  "{{/guardrails-guardrail}}";
33
+ // Skills block (i-031) β€” a referenced Skill COMPOSES into the system prompt,
34
+ // exactly like Guardrails: a Mustache section over the dep-filtered
35
+ // `agentskills-skill` list, inlining each skill's SKILL.md body. Before this
36
+ // fix a wired Skill was inert (in context, rendered by no layout). Skills
37
+ // land AFTER the soul and BEFORE guardrails; a skill-less agent renders the
38
+ // empty section to nothing, composing byte-identically to before. 1:1 with
39
+ // Python `_SKILLS_BLOCK`.
40
+ const SKILLS_BLOCK = "{{#agentskills-skill}}" +
41
+ "## Skill: {{name}}\n" +
42
+ "{{#description}}_{{description}}_\n\n{{/description}}" +
43
+ "{{{instruction}}}\n\n" +
44
+ "{{/agentskills-skill}}";
33
45
  // instruction-first (a.k.a. "default") β€” historic order: instruction, soul,
34
- // guardrails. IS the kind default template.
35
- const LAYOUT_INSTRUCTION_FIRST = "{{{agent.instruction}}}\n\n{{{soul_content}}}\n\n" + GUARDRAILS_BLOCK;
36
- // persona-first β€” Soul leads, then instruction, then guardrails.
37
- const LAYOUT_PERSONA_FIRST = "{{{soul_content}}}\n\n{{{agent.instruction}}}\n\n" + GUARDRAILS_BLOCK;
46
+ // skills, guardrails. IS the kind default template.
47
+ const LAYOUT_INSTRUCTION_FIRST = "{{{agent.instruction}}}\n\n{{{soul_content}}}\n\n" + SKILLS_BLOCK + GUARDRAILS_BLOCK;
48
+ // persona-first β€” Soul leads, then instruction, then skills, then guardrails.
49
+ const LAYOUT_PERSONA_FIRST = "{{{soul_content}}}\n\n{{{agent.instruction}}}\n\n" + SKILLS_BLOCK + GUARDRAILS_BLOCK;
38
50
  const AGENT_LAYOUTS = {
39
51
  default: LAYOUT_INSTRUCTION_FIRST,
40
52
  "instruction-first": LAYOUT_INSTRUCTION_FIRST,
@@ -342,8 +354,9 @@ class AgentKind extends KindBase {
342
354
  promptTemplate() {
343
355
  // IS the `instruction-first` / `default` named layout β€” the kind default
344
356
  // template and the `default` layout are one string, so an agent with no
345
- // `layout:` composes identically. (Now includes the guardrails block,
346
- // matching Python β€” see the layout-constants comment above.)
357
+ // `layout:` composes identically. Includes the skills block (i-031) and
358
+ // the guardrails block β€” Soul, Skills, and Guardrails all compose into the
359
+ // prompt. See the layout-constants comment above.
347
360
  return LAYOUT_INSTRUCTION_FIRST;
348
361
  }
349
362
  layoutTemplate(name) {
@@ -0,0 +1,114 @@
1
+ # Membership β€” the RBAC join (record plane): a user's role at an org- or
2
+ # project-scope. Named `Membership` (NOT the bare tenant Kind `TenantMembership`,
3
+ # alias tenant-membership, which links a user to a platform-level Tenant) β€” this
4
+ # one is the portfolio access grant a user holds within a tenant's own
5
+ # Organization / Project graph. The role names mirror the standard ladder
6
+ # (owner / admin / member / guest, highest-role-wins, org-owner superuser); a
7
+ # custom-role follow-up would relax this enum to a free reference into the Role
8
+ # Kind (see role.kind.yaml β€” the ladder is data so it can be extended).
9
+ #
10
+ # TENANTED β€” a membership is per-tenant access data, NOT a shared `_lib` default.
11
+ # Deliberately NOT inheritable (never in DEFAULT_INHERITABLE_KINDS_V1); TENANTED
12
+ # is correct.
13
+ #
14
+ # PARITY-CRITICAL package data: the byte-identical mirror lives at
15
+ # packages/sdk-{py,ts}/{dna/extensions,src/extensions}/portfolio/kinds/
16
+ # membership.kind.yaml (tests/test_descriptor_hash_parity.py enforces).
17
+ apiVersion: github.com/ruinosus/dna/core/v1
18
+ kind: KindDefinition
19
+ metadata:
20
+ name: membership
21
+ spec:
22
+ target_api_version: github.com/ruinosus/dna/portfolio/v1
23
+ target_kind: Membership
24
+ alias: portfolio-membership
25
+ origin: github.com/ruinosus/dna/portfolio
26
+ tenant_scope: tenanted
27
+ plane: record
28
+ prompt_target: false
29
+ flatten_in_context: false
30
+ prompt_target_priority: 0
31
+ storage:
32
+ type: yaml
33
+ container: memberships
34
+ schema:
35
+ type: object
36
+ additionalProperties: false
37
+ required:
38
+ - user
39
+ - scope_type
40
+ - scope_ref
41
+ - role
42
+ properties:
43
+ user:
44
+ type: string
45
+ description: The member's identity β€” an email or stable user id.
46
+ scope_type:
47
+ type: string
48
+ enum:
49
+ - org
50
+ - project
51
+ description: What the grant is scoped to β€” an Organization (org) or a single
52
+ Project (project).
53
+ scope_ref:
54
+ type: string
55
+ description: The Organization or Project name this grant applies to (paired
56
+ with scope_type).
57
+ role:
58
+ type: string
59
+ enum:
60
+ - owner
61
+ - admin
62
+ - member
63
+ - guest
64
+ description: The role granted at this scope β€” the standard ladder
65
+ (owner > admin > member > guest). Resolution is highest-role-wins across
66
+ a user's memberships, with the org owner a superuser.
67
+ status:
68
+ type: string
69
+ enum:
70
+ - invited
71
+ - active
72
+ default: invited
73
+ description: Invitation lifecycle β€” invited (pending acceptance) or active.
74
+ invited_at:
75
+ type:
76
+ - string
77
+ - 'null'
78
+ description: ISO-8601 timestamp of the invite, stamped by the writer (not
79
+ defaulted here).
80
+ describe: '{user} β†’ {scope_ref} ({role})'
81
+ summary:
82
+ user: ''
83
+ scope_type: ''
84
+ scope_ref: ''
85
+ role: ''
86
+ status: invited
87
+ ui:
88
+ mode: govern
89
+ label:
90
+ en: Memberships
91
+ pt-BR: Membros
92
+ icon: πŸ‘€
93
+ description:
94
+ en: RBAC grants β€” a user's role at an org or project scope (highest-role-wins).
95
+ pt-BR: ConcessΓ΅es RBAC β€” o papel de um usuΓ‘rio num escopo de org ou projeto
96
+ (maior-papel-vence).
97
+ routes:
98
+ list: docs/Membership
99
+ detail: docs/Membership/:name
100
+ permissions:
101
+ list: any
102
+ detail: any
103
+ in_sidebar: true
104
+ display_order: 61
105
+ ascii_icon: πŸ‘€
106
+ display_label: Memberships
107
+ docs: A Membership is the RBAC join β€” a user's role at an org- or project-scope
108
+ within a tenant's portfolio. It carries the user (email / id), the scope_type
109
+ (org / project) and scope_ref it applies to, the role from the standard ladder
110
+ (owner > admin > member > guest, highest-role-wins, org-owner superuser), and
111
+ an invitation status (invited / active), as per-tenant declarative data. It is
112
+ distinct from the platform-level TenantMembership (which links a user to a
113
+ provisioning Tenant); this grants access inside the tenant's own Organization /
114
+ Project graph.
@@ -0,0 +1,99 @@
1
+ # Organization β€” a tenant's org profile (record plane; the top-level container).
2
+ # One doc per tenant: the human-facing identity of the organization whose
3
+ # portfolio of Projects the DNA Cloud console aggregates. Named `Organization`
4
+ # (the enterprise-familiar top-level term β€” GitHub/Azure DevOps) so procurement
5
+ # recognizes it; it is the tenant's OWN profile, distinct from the platform-level
6
+ # `Tenant` provisioning identity Kind (GLOBAL, `_lib`) β€” this one is the editable
7
+ # org profile that lives inside the tenant's own portfolio.
8
+ #
9
+ # TENANTED β€” the org profile is per-tenant user data (one Organization per
10
+ # tenant), NOT a shared `_lib` default consumed by other scopes. It is
11
+ # deliberately NOT inheritable (never in DEFAULT_INHERITABLE_KINDS_V1); TENANTED
12
+ # is the correct tenancy for per-tenant data without a platform default.
13
+ #
14
+ # PARITY-CRITICAL package data: the byte-identical mirror lives at
15
+ # packages/sdk-{py,ts}/{dna/extensions,src/extensions}/portfolio/kinds/
16
+ # organization.kind.yaml (tests/test_descriptor_hash_parity.py enforces).
17
+ apiVersion: github.com/ruinosus/dna/core/v1
18
+ kind: KindDefinition
19
+ metadata:
20
+ name: organization
21
+ spec:
22
+ target_api_version: github.com/ruinosus/dna/portfolio/v1
23
+ target_kind: Organization
24
+ alias: portfolio-org
25
+ origin: github.com/ruinosus/dna/portfolio
26
+ tenant_scope: tenanted
27
+ plane: record
28
+ prompt_target: false
29
+ flatten_in_context: false
30
+ prompt_target_priority: 0
31
+ storage:
32
+ type: yaml
33
+ container: organizations
34
+ schema:
35
+ type: object
36
+ additionalProperties: false
37
+ required:
38
+ - name
39
+ - slug
40
+ properties:
41
+ name:
42
+ type: string
43
+ description: The organization's canonical name. The doc name SHOULD equal
44
+ this.
45
+ slug:
46
+ type: string
47
+ description: URL-safe identity for the org, e.g. acme-corp. Used in routes
48
+ and as a stable handle for the tenant's portfolio.
49
+ display_name:
50
+ type:
51
+ - string
52
+ - 'null'
53
+ description: Human-facing name shown in the console. Falls back to name.
54
+ plan_ref:
55
+ type:
56
+ - string
57
+ - 'null'
58
+ description: The DNA Cloud Tier / TenantPlan this org is on (a Tier tier_id
59
+ or TenantPlan name). Null falls back to Free. The billing→enforcement
60
+ bridge reads it; the OSS SDK only stores it.
61
+ created_at:
62
+ type:
63
+ - string
64
+ - 'null'
65
+ description: ISO-8601 timestamp, stamped by the writer (not defaulted here).
66
+ describe: '{name}'
67
+ summary:
68
+ name: ''
69
+ slug: ''
70
+ plan_ref: null
71
+ ui:
72
+ mode: govern
73
+ label:
74
+ en: Organizations
75
+ pt-BR: OrganizaΓ§Γ΅es
76
+ icon: πŸ›οΈ
77
+ description:
78
+ en: The tenant's org profile (the top-level container the portfolio console
79
+ aggregates).
80
+ pt-BR: O perfil da organizaΓ§Γ£o do tenant (o container de topo que o console
81
+ de portfΓ³lio agrega).
82
+ routes:
83
+ list: docs/Organization
84
+ detail: docs/Organization/:name
85
+ permissions:
86
+ list: any
87
+ detail: any
88
+ in_sidebar: true
89
+ display_order: 58
90
+ ascii_icon: πŸ›οΈ
91
+ display_label: Organizations
92
+ docs: An Organization is the tenant's own org profile β€” the enterprise-familiar
93
+ top-level container (as in GitHub / Azure DevOps) whose portfolio of Projects
94
+ the DNA Cloud console aggregates. It carries the org name, a URL-safe slug, an
95
+ optional display name, and a plan_ref to the DNA Cloud Tier / TenantPlan the
96
+ org is on, as per-tenant declarative data. One Organization per tenant; it is
97
+ distinct from the platform-level Tenant provisioning identity Kind (the
98
+ editable org profile inside the tenant's own portfolio, not the GLOBAL
99
+ identity row).
@@ -0,0 +1,124 @@
1
+ # Project β€” the multi-repo development-space container (record plane; the KEY
2
+ # Kind of the portfolio model). Formalizes the marriage of three primitives the
3
+ # DNA already has: (a) a SDLC board scope (convention <slug>-development), (b) one
4
+ # or more IntelSources (the intelligence layer observes them), and (c) scoped
5
+ # memory. This is the Azure DevOps-style container: a Project OWNS its board +
6
+ # intel + memory and is the permission boundary; repos are ATTACHED BY REFERENCE.
7
+ #
8
+ # Nβ€”N Repo↔Project (decided with the owner): the edge lives HERE, as `repo_refs`
9
+ # (a list of Repo names). A repo can belong to MULTIPLE Projects without being
10
+ # duplicated; the Repo Kind deliberately carries NO project back-ref (single
11
+ # source of truth for the edge β€” the Project side). Modelling the reverse as a
12
+ # join Kind was considered and rejected as premature: a list of names on the
13
+ # Project is the smallest thing that expresses Nβ€”N here.
14
+ #
15
+ # TENANTED β€” a Project is the tenant's own development space (per-tenant data),
16
+ # NOT a shared `_lib` default. Deliberately NOT inheritable (never in
17
+ # DEFAULT_INHERITABLE_KINDS_V1); TENANTED is correct.
18
+ #
19
+ # PARITY-CRITICAL package data: the byte-identical mirror lives at
20
+ # packages/sdk-{py,ts}/{dna/extensions,src/extensions}/portfolio/kinds/
21
+ # project.kind.yaml (tests/test_descriptor_hash_parity.py enforces).
22
+ apiVersion: github.com/ruinosus/dna/core/v1
23
+ kind: KindDefinition
24
+ metadata:
25
+ name: project
26
+ spec:
27
+ target_api_version: github.com/ruinosus/dna/portfolio/v1
28
+ target_kind: Project
29
+ alias: portfolio-project
30
+ origin: github.com/ruinosus/dna/portfolio
31
+ tenant_scope: tenanted
32
+ plane: record
33
+ prompt_target: false
34
+ flatten_in_context: false
35
+ prompt_target_priority: 0
36
+ storage:
37
+ type: yaml
38
+ container: projects
39
+ schema:
40
+ type: object
41
+ additionalProperties: false
42
+ required:
43
+ - name
44
+ - slug
45
+ properties:
46
+ name:
47
+ type: string
48
+ description: The project's canonical name. The doc name SHOULD equal this.
49
+ slug:
50
+ type: string
51
+ description: URL-safe identity for the project, e.g. copiloto-medico. Used
52
+ in routes and to derive the board_scope by convention.
53
+ org_ref:
54
+ type:
55
+ - string
56
+ - 'null'
57
+ description: The Organization (name) this project belongs to. Null while
58
+ unassigned.
59
+ repo_refs:
60
+ type: array
61
+ items:
62
+ type: string
63
+ description: Repo names attached to this project (the Nβ€”N edge β€” a repo may
64
+ appear on multiple projects). The edge lives on the Project side only.
65
+ board_scope:
66
+ type:
67
+ - string
68
+ - 'null'
69
+ description: The SDLC scope this project owns (convention <slug>-development).
70
+ Where its Stories / Issues / Epics live.
71
+ intel_source_refs:
72
+ type: array
73
+ items:
74
+ type: string
75
+ description: IntelSource names the intelligence layer observes for this
76
+ project (the sources feeding its insight stream).
77
+ visibility:
78
+ type: string
79
+ enum:
80
+ - private
81
+ - shared
82
+ default: private
83
+ description: Who can see the project β€” private (org-internal) or shared
84
+ (visible across the portfolio).
85
+ created_at:
86
+ type:
87
+ - string
88
+ - 'null'
89
+ description: ISO-8601 timestamp, stamped by the writer (not defaulted here).
90
+ describe: '{name}'
91
+ summary:
92
+ name: ''
93
+ slug: ''
94
+ org_ref: null
95
+ visibility: private
96
+ ui:
97
+ mode: govern
98
+ label:
99
+ en: Projects
100
+ pt-BR: Projetos
101
+ icon: πŸ“
102
+ description:
103
+ en: Multi-repo development-space containers (board + intel + memory; repos
104
+ attached by reference).
105
+ pt-BR: Containers de espaΓ§o-de-desenvolvimento multi-repo (board + inteligΓͺncia
106
+ + memΓ³ria; repos anexados por referΓͺncia).
107
+ routes:
108
+ list: docs/Project
109
+ detail: docs/Project/:name
110
+ permissions:
111
+ list: any
112
+ detail: any
113
+ in_sidebar: true
114
+ display_order: 59
115
+ ascii_icon: πŸ“
116
+ display_label: Projects
117
+ docs: A Project is the multi-repo development-space container β€” the key Kind of
118
+ the portfolio model. It owns a SDLC board scope (convention <slug>-development),
119
+ one or more IntelSources the intelligence layer observes, and scoped memory,
120
+ and it is the permission boundary. Repos are attached BY REFERENCE via repo_refs
121
+ (an Nβ€”N edge kept on the Project side β€” a repo can belong to many projects
122
+ without duplication; Repo carries no project back-ref). A Project has a
123
+ visibility (private / shared) and an org_ref to its Organization, as per-tenant
124
+ declarative data.
@@ -0,0 +1,99 @@
1
+ # Repo β€” a code repository the portfolio references (record plane). A source of
2
+ # code attached to N Projects via Project.repo_refs (the Nβ€”N edge lives on the
3
+ # Project side). A Repo deliberately carries NO project back-ref: the edge has a
4
+ # single source of truth (the Project), so a repo shared across projects is never
5
+ # duplicated and never carries a stale reverse list. Resolving "which projects
6
+ # reference this repo" is a query over Projects, not a stored field.
7
+ #
8
+ # TENANTED β€” a repo is part of the tenant's own portfolio (per-tenant data), NOT
9
+ # a shared `_lib` default. Deliberately NOT inheritable (never in
10
+ # DEFAULT_INHERITABLE_KINDS_V1); TENANTED is correct.
11
+ #
12
+ # PARITY-CRITICAL package data: the byte-identical mirror lives at
13
+ # packages/sdk-{py,ts}/{dna/extensions,src/extensions}/portfolio/kinds/
14
+ # repo.kind.yaml (tests/test_descriptor_hash_parity.py enforces).
15
+ apiVersion: github.com/ruinosus/dna/core/v1
16
+ kind: KindDefinition
17
+ metadata:
18
+ name: repo
19
+ spec:
20
+ target_api_version: github.com/ruinosus/dna/portfolio/v1
21
+ target_kind: Repo
22
+ alias: portfolio-repo
23
+ origin: github.com/ruinosus/dna/portfolio
24
+ tenant_scope: tenanted
25
+ plane: record
26
+ prompt_target: false
27
+ flatten_in_context: false
28
+ prompt_target_priority: 0
29
+ storage:
30
+ type: yaml
31
+ container: repos
32
+ schema:
33
+ type: object
34
+ additionalProperties: false
35
+ required:
36
+ - name
37
+ properties:
38
+ name:
39
+ type: string
40
+ description: The repo name, e.g. copiloto-medico. The doc name SHOULD equal
41
+ this; Project.repo_refs point at it.
42
+ url:
43
+ type:
44
+ - string
45
+ - 'null'
46
+ description: Clone / browse URL of the repository. Null when the name alone
47
+ identifies it.
48
+ provider:
49
+ type: string
50
+ enum:
51
+ - github
52
+ - gitlab
53
+ - azure-devops
54
+ - other
55
+ default: github
56
+ description: Where the repo is hosted β€” github, gitlab, azure-devops, or
57
+ other.
58
+ default_branch:
59
+ type:
60
+ - string
61
+ - 'null'
62
+ description: The repo's default branch, e.g. main. Null when unknown.
63
+ created_at:
64
+ type:
65
+ - string
66
+ - 'null'
67
+ description: ISO-8601 timestamp, stamped by the writer (not defaulted here).
68
+ describe: '{name}'
69
+ summary:
70
+ name: ''
71
+ provider: ''
72
+ default_branch: null
73
+ ui:
74
+ mode: govern
75
+ label:
76
+ en: Repos
77
+ pt-BR: RepositΓ³rios
78
+ icon: πŸ“¦
79
+ description:
80
+ en: Code repositories the portfolio references (attached to projects by
81
+ reference, Nβ€”N).
82
+ pt-BR: RepositΓ³rios de cΓ³digo que o portfΓ³lio referencia (anexados a projetos
83
+ por referΓͺncia, Nβ€”N).
84
+ routes:
85
+ list: docs/Repo
86
+ detail: docs/Repo/:name
87
+ permissions:
88
+ list: any
89
+ detail: any
90
+ in_sidebar: true
91
+ display_order: 60
92
+ ascii_icon: πŸ“¦
93
+ display_label: Repos
94
+ docs: A Repo is a code repository the portfolio references β€” its name, url,
95
+ provider (github / gitlab / azure-devops / other) and default_branch, as
96
+ per-tenant declarative data. It is attached to N Projects via Project.repo_refs
97
+ (the Nβ€”N edge lives on the Project side); a Repo carries no project back-ref,
98
+ so a repo shared across projects is never duplicated. "Which projects use this
99
+ repo" is a query over Projects, not a stored reverse list.
@@ -0,0 +1,96 @@
1
+ # Role β€” the RBAC role ladder AS DATA (record plane; the DNA thesis: everything
2
+ # declarative). Instead of a hardcoded enum, each rung of the ladder is a Role
3
+ # doc carrying its rank (higher = more access) and the capabilities it grants, so
4
+ # the ladder is EXTENSIBLE β€” a tenant can add a custom role between member and
5
+ # admin without a code change. The four standard rungs (owner / admin / member /
6
+ # guest) ship as seed / example docs (examples/dna-cloud/.dna/.../roles/).
7
+ # highest-role-wins compares `rank`; the org owner is a superuser above the ladder.
8
+ #
9
+ # TENANTED β€” a tenant owns its OWN role set (seeded from the standard ladder, then
10
+ # customizable), NOT a shared `_lib` default. Deliberately NOT inheritable (never
11
+ # in DEFAULT_INHERITABLE_KINDS_V1); TENANTED is correct β€” this is why the standard
12
+ # rungs ship as per-tenant seed docs rather than a GLOBAL registry.
13
+ #
14
+ # PARITY-CRITICAL package data: the byte-identical mirror lives at
15
+ # packages/sdk-{py,ts}/{dna/extensions,src/extensions}/portfolio/kinds/
16
+ # role.kind.yaml (tests/test_descriptor_hash_parity.py enforces).
17
+ apiVersion: github.com/ruinosus/dna/core/v1
18
+ kind: KindDefinition
19
+ metadata:
20
+ name: role
21
+ spec:
22
+ target_api_version: github.com/ruinosus/dna/portfolio/v1
23
+ target_kind: Role
24
+ alias: portfolio-role
25
+ origin: github.com/ruinosus/dna/portfolio
26
+ tenant_scope: tenanted
27
+ plane: record
28
+ prompt_target: false
29
+ flatten_in_context: false
30
+ prompt_target_priority: 0
31
+ storage:
32
+ type: yaml
33
+ container: roles
34
+ schema:
35
+ type: object
36
+ additionalProperties: false
37
+ required:
38
+ - role_id
39
+ - display_name
40
+ - rank
41
+ properties:
42
+ role_id:
43
+ type: string
44
+ description: Canonical role id, e.g. owner / admin / member / guest. The doc
45
+ name SHOULD equal this; Membership.role references it.
46
+ display_name:
47
+ type: string
48
+ description: Human-facing role name, e.g. Owner, Admin, Member, Guest.
49
+ rank:
50
+ type: integer
51
+ description: Ladder rank β€” higher = more access. highest-role-wins compares
52
+ this across a user's memberships.
53
+ capabilities:
54
+ type: array
55
+ items:
56
+ type: string
57
+ description: The grants this role unlocks (e.g. project.write, member.invite,
58
+ billing.manage). The permission checker reads them from here.
59
+ can_delete:
60
+ type: boolean
61
+ default: true
62
+ description: False for the built-in ladder rungs that must not be removed
63
+ (e.g. owner); true for custom roles a tenant adds.
64
+ describe: '{display_name} (rank {rank})'
65
+ summary:
66
+ role_id: ''
67
+ display_name: ''
68
+ rank: 0
69
+ can_delete: true
70
+ ui:
71
+ mode: govern
72
+ label:
73
+ en: Roles
74
+ pt-BR: PapΓ©is
75
+ icon: 🎚️
76
+ description:
77
+ en: The RBAC role ladder as data (extensible β€” add a custom rung without code).
78
+ pt-BR: A escada de papΓ©is RBAC como dado (extensΓ­vel β€” adicione um degrau sem
79
+ cΓ³digo).
80
+ routes:
81
+ list: docs/Role
82
+ detail: docs/Role/:name
83
+ permissions:
84
+ list: any
85
+ detail: any
86
+ in_sidebar: true
87
+ display_order: 62
88
+ ascii_icon: 🎚️
89
+ display_label: Roles
90
+ docs: A Role is one rung of the RBAC ladder expressed as data β€” its role_id,
91
+ display_name, rank (higher = more access), the capabilities it grants, and a
92
+ can_delete flag protecting built-in rungs. Modelling the ladder as data (not a
93
+ hardcoded enum) makes it extensible β€” a tenant can add a custom role without a
94
+ code change, and highest-role-wins simply compares rank. The four standard
95
+ rungs (owner / admin / member / guest) ship as per-tenant seed docs; the org
96
+ owner is a superuser above the ladder.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * PortfolioExtension β€” DNA Cloud portfolio-console data foundation.
3
+ *
4
+ * 1:1 parity with Python `dna.extensions.portfolio`.
5
+ *
6
+ * Registers 5 record Kinds, from descriptors (F3 β€” record Kinds are data, not
7
+ * classes). The accepted model (adr-portfolio-project-model): an Organization
8
+ * (the tenant boundary) owns Projects; a Project is a multi-repo CONTAINER that
9
+ * owns its board scope + intel sources + memory; RBAC is the standard ladder.
10
+ *
11
+ * - Organization (`portfolio-org`) β€” the tenant's org profile, the
12
+ * enterprise-familiar top-level container the console aggregates. One per
13
+ * tenant. Distinct from the platform-level `Tenant` provisioning identity
14
+ * Kind (GLOBAL); this is the editable profile inside the tenant's portfolio.
15
+ * - Project (`portfolio-project`) β€” the KEY Kind: the multi-repo
16
+ * development-space container that OWNS a board scope (`<slug>-development`),
17
+ * IntelSources and memory, and is the permission boundary. Repos are
18
+ * attached BY REFERENCE via `repo_refs` (the Nβ€”N edge lives on the Project
19
+ * side).
20
+ * - Repo (`portfolio-repo`) β€” a code repository the portfolio references.
21
+ * Attached to N Projects via `Project.repo_refs`; carries NO project
22
+ * back-ref (single source of truth for the Nβ€”N edge β†’ no duplication).
23
+ * - Membership (`portfolio-membership`) β€” the RBAC join: a user's role at an
24
+ * org- or project-scope (standard ladder owner > admin > member > guest,
25
+ * highest-role-wins, org-owner superuser). Distinct from the tenant Kind
26
+ * `TenantMembership` (which links a user to a provisioning Tenant).
27
+ * - Role (`portfolio-role`) β€” the role ladder AS DATA (the DNA thesis):
28
+ * each rung a doc with `rank` + `capabilities`, so the ladder is extensible
29
+ * (custom roles) rather than a hardcoded enum. The four standard rungs ship
30
+ * as per-tenant seed docs (examples/dna-cloud/.dna/.../roles/).
31
+ *
32
+ * All 5 are TENANTED β€” per-tenant portfolio data, NOT shared `_lib` defaults,
33
+ * and deliberately NOT inheritable (never in DEFAULT_INHERITABLE_KINDS_V1), so
34
+ * TENANTED is the correct tenancy.
35
+ *
36
+ * This is the data foundation ONLY β€” the console UI, the resolve_role helper
37
+ * (highest-role-wins + org-owner superuser) and the enterprise SSO/SCIM layer
38
+ * land in later stories; here we ship just the five data Kinds.
39
+ */
40
+ import type { ExtensionHost, Extension } from "../kernel/protocols.js";
41
+ export declare class PortfolioExtension implements Extension {
42
+ name: string;
43
+ version: string;
44
+ register(kernel: ExtensionHost): void;
45
+ }