endeavor-nova-wire 0.1.0.dev394__tar.gz

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 (27) hide show
  1. endeavor_nova_wire-0.1.0.dev394/LICENSE +21 -0
  2. endeavor_nova_wire-0.1.0.dev394/PKG-INFO +9 -0
  3. endeavor_nova_wire-0.1.0.dev394/pyproject.toml +18 -0
  4. endeavor_nova_wire-0.1.0.dev394/pyproject.toml.orig +18 -0
  5. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/__init__.py +4 -0
  6. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/access.py +105 -0
  7. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/actions.py +153 -0
  8. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/agents.py +54 -0
  9. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/apps.py +253 -0
  10. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/auth.py +78 -0
  11. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/base.py +26 -0
  12. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/connections.py +182 -0
  13. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/drafts.py +70 -0
  14. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/exports.py +48 -0
  15. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/extractions.py +84 -0
  16. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/files.py +35 -0
  17. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/inbox.py +89 -0
  18. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/model.py +289 -0
  19. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/ontology.py +145 -0
  20. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/orgs.py +61 -0
  21. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/py.typed +0 -0
  22. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/query.py +434 -0
  23. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/snapshots.py +55 -0
  24. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/telemetry.py +233 -0
  25. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/tools.py +175 -0
  26. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/views.py +655 -0
  27. endeavor_nova_wire-0.1.0.dev394/src/nova_wire/workbench.py +521 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Endeavor AI, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: endeavor-nova-wire
3
+ Version: 0.1.0.dev394
4
+ Summary: Nova wire models: the request and response shapes of the Nova API.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Dist: email-validator>=2
8
+ Requires-Dist: pydantic>=2.11,<3
9
+ Requires-Python: >=3.14
@@ -0,0 +1,18 @@
1
+ [project]
2
+ name = "endeavor-nova-wire"
3
+ version = "0.1.0.dev394"
4
+ description = "Nova wire models: the request and response shapes of the Nova API."
5
+ license = "MIT"
6
+ license-files = ["LICENSE"]
7
+ requires-python = ">=3.14"
8
+ dependencies = [
9
+ "email-validator>=2",
10
+ "pydantic>=2.11,<3",
11
+ ]
12
+
13
+ [build-system]
14
+ requires = ["uv_build>=0.11.17,<0.12"]
15
+ build-backend = "uv_build"
16
+
17
+ [tool.uv.build-backend]
18
+ module-name = "nova_wire"
@@ -0,0 +1,18 @@
1
+ [project]
2
+ name = "endeavor-nova-wire"
3
+ version = "0.1.0.dev394"
4
+ description = "Nova wire models: the request and response shapes of the Nova API."
5
+ license = "MIT"
6
+ license-files = ["LICENSE"]
7
+ requires-python = ">=3.14"
8
+ dependencies = [
9
+ "email-validator>=2",
10
+ "pydantic>=2.11,<3",
11
+ ]
12
+
13
+ [build-system]
14
+ requires = ["uv_build>=0.11.17,<0.12"]
15
+ build-backend = "uv_build"
16
+
17
+ [tool.uv.build-backend]
18
+ module-name = "nova_wire"
@@ -0,0 +1,4 @@
1
+ """Request and response models shared by the Nova API and its clients.
2
+
3
+ Pure pydantic: this package has no logic, so the CLI can depend on it alone.
4
+ """
@@ -0,0 +1,105 @@
1
+ """App access over the wire: the synced spec, members, app sessions and ``/me``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+ from pydantic import BaseModel, EmailStr, Field
9
+
10
+
11
+ class AccessSync(BaseModel):
12
+ spec: dict[str, Any]
13
+ """The app's ``access.yaml``, as data."""
14
+ allow_breaking: bool = False
15
+ """Allow narrowing roles that have members. Removing them is never allowed."""
16
+
17
+
18
+ class AccessOut(BaseModel):
19
+ spec: dict[str, Any] | None
20
+ version: str | None
21
+ synced_at: datetime | None
22
+ synced_by: str | None
23
+ members: dict[str, int] = {}
24
+ """Members per role id."""
25
+
26
+
27
+ class AccessSynced(AccessOut):
28
+ diff: dict[str, Any]
29
+ warnings: list[str] = []
30
+
31
+
32
+ class MemberGrant(BaseModel):
33
+ role: str = Field(min_length=1, max_length=64)
34
+ attributes: dict[str, str] = {}
35
+
36
+
37
+ class AppMemberOut(BaseModel):
38
+ user_id: str
39
+ email: str
40
+ name: str
41
+ role: str
42
+ attributes: dict[str, str]
43
+ created_at: datetime
44
+
45
+
46
+ class EffectiveAccess(BaseModel):
47
+ """What one member can do and read in an app, attributes filled in."""
48
+
49
+ user_id: str
50
+ email: str
51
+ name: str
52
+ app: str
53
+ role: str
54
+ via: str
55
+ """``member`` for an app member; ``tenant-admin`` for an org owner or admin."""
56
+ permissions: list[str]
57
+ max_sensitivity: str
58
+ objects: list[str] | None
59
+ """Readable object types; ``None`` for every type."""
60
+ rows: dict[str, str]
61
+ """Object type → the row filter applied, with the member's attributes bound."""
62
+ attributes: dict[str, str]
63
+
64
+
65
+ class AppLogin(BaseModel):
66
+ email: EmailStr
67
+ password: str
68
+
69
+
70
+ class AppSession(BaseModel):
71
+ token: str
72
+ expires_at: datetime
73
+ access: EffectiveAccess
74
+
75
+
76
+ # -- checks on an access.yaml the client holds -----------------------------------------
77
+
78
+
79
+ class AccessCheck(BaseModel):
80
+ spec: str
81
+ """The ``access.yaml`` text."""
82
+ code_permissions: dict[str, list[str]] | None = None
83
+ """Permission → where the app's code requires it (``file:line``)."""
84
+ ontology_yaml: str | None = None
85
+ """Check against this spec; defaults to the org's published version, if any."""
86
+ snapshot_id: str | None = None
87
+ ontology_source: str | None = None
88
+ """Echoed back as ``ontology`` so the caller can say what was checked against."""
89
+ app: str | None = None
90
+ """The app's slug: once registered with object types of its own, those are
91
+ merged into the ontology checked against, so roles may name them."""
92
+
93
+
94
+ class AccessExplain(AccessCheck):
95
+ role: str
96
+ attributes: dict[str, str] = Field(default_factory=dict)
97
+ query: dict[str, Any] | None = None
98
+
99
+
100
+ class AccessCompare(BaseModel):
101
+ spec: str
102
+ first: str
103
+ second: str
104
+ ontology_yaml: str | None = None
105
+ app: str | None = None
@@ -0,0 +1,153 @@
1
+ """Actions over the wire: what an app's caller may run, one call, and its audit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class ActionParamOut(BaseModel):
12
+ id: str
13
+ label: str
14
+ description: str | None = None
15
+ type: str
16
+ format: str | None = None
17
+ """``file`` when the parameter takes the id of a file uploaded to the app."""
18
+ values: list[str] | None = None
19
+ required: bool = True
20
+ min: float | None = None
21
+ max: float | None = None
22
+ min_length: int | None = None
23
+ max_length: int | None = None
24
+ pattern: str | None = None
25
+ items: ActionParamOut | None = None
26
+ """For a ``list``: what each item is."""
27
+ fields: list[ActionParamOut] | None = None
28
+ """For an ``object``: its fields."""
29
+ max_items: int | None = None
30
+ max_bytes: int | None = None
31
+
32
+
33
+ class ActionAgentOut(BaseModel):
34
+ """For an agent action: what Nova's agent reads and writes through."""
35
+
36
+ reads: list[str]
37
+ """The object types it may query, the object's own among them."""
38
+ writes: str
39
+ """The action it ends with, run as the app."""
40
+ triggers: list[str] = Field(default_factory=list)
41
+ """When it also runs without being asked: ``created``, for each new object
42
+ of its type."""
43
+
44
+
45
+ class ActionOut(BaseModel):
46
+ """One action of the app's ontology, and whether the caller may run it."""
47
+
48
+ id: str
49
+ label: str
50
+ description: str | None = None
51
+ object_type: str
52
+ effect: str
53
+ """``create``, ``modify``, ``delete`` or ``agent``."""
54
+ permission: str | None = None
55
+ """The permission a role needs; ``None`` means every permission (``*``)."""
56
+ params: list[ActionParamOut] = Field(default_factory=list)
57
+ writes: list[str] = Field(default_factory=list)
58
+ """The properties it sets."""
59
+ key: list[str] = Field(default_factory=list)
60
+ """The columns a modify or delete identifies the object by."""
61
+ allowed: bool
62
+ """Whether the caller's role may run it."""
63
+ tool: str | None = None
64
+ """The tool it writes to (``linear``), for an action that calls one of a
65
+ synced type's operations: it changes data outside Nova."""
66
+ calls: str | None = None
67
+ """That operation, ``<tool>.<operation>``."""
68
+ agent: ActionAgentOut | None = None
69
+ """For an agent action: Nova carries it out with the organization's model,
70
+ in the background; its run is at ``agent_runs/{run_id}``."""
71
+
72
+
73
+ class ActionRun(BaseModel):
74
+ """One call: the parameters, and the object's key for modify and delete."""
75
+
76
+ params: dict[str, Any] = Field(default_factory=dict)
77
+ key: Any = None
78
+ """The primary key value, a list for a composite key, or a mapping by column."""
79
+ dry_run: bool = False
80
+ """Compile and report the SQL (or the tool's requests) without running it."""
81
+ idempotency_key: str | None = Field(
82
+ default=None, min_length=1, max_length=128, pattern=r"^[A-Za-z0-9_.:-]+$"
83
+ )
84
+ """The same key again returns the first run's result instead of writing
85
+ twice, and resumes a tool write whose answer was lost. Without one, each
86
+ call is its own run."""
87
+ force: bool = False
88
+ """Resend a tool write whose outcome is unknown and that can't be looked
89
+ up, accepting that it may be done twice."""
90
+
91
+
92
+ class ToolRequestOut(BaseModel):
93
+ """One request a tool write sent (or, for a dry run, would send)."""
94
+
95
+ step: str
96
+ method: str
97
+ url: str
98
+ status: int | None = None
99
+ """The tool's answer; ``None`` for a dry run, or when none came."""
100
+ body: Any = None
101
+ """For a dry run: what would be sent."""
102
+ found: bool = False
103
+ """Already done: a retried run found it (``find``) instead of sending it."""
104
+
105
+
106
+ class ActionResultOut(BaseModel):
107
+ action: str
108
+ object_type: str
109
+ effect: str
110
+ status: str
111
+ """``ok``; ``unchanged`` when a modify or delete found nothing to change;
112
+ ``dry_run`` when nothing ran; ``queued`` for an agent action, which Nova
113
+ carries out in the background (``run_id``)."""
114
+ rows: int
115
+ key: dict[str, Any] | None = None
116
+ """The key of the object written, when the warehouse (or the tool) reports
117
+ it."""
118
+ sql: str = ""
119
+ elapsed_ms: int
120
+ tool: str | None = None
121
+ """For a tool write: the tool."""
122
+ requests: list[ToolRequestOut] = Field(default_factory=list)
123
+ """For a tool write: what was sent, in order."""
124
+ idempotency_key: str | None = None
125
+ """The run's key: retry with it to resume a write whose answer was lost."""
126
+ run_id: str | None = None
127
+ """For an agent action: the run to poll (``GET .../agent-runs/{id}``)."""
128
+
129
+
130
+ class ActionAuditOut(BaseModel):
131
+ id: str
132
+ principal: str
133
+ app_id: str | None = None
134
+ app_role: str | None = None
135
+ ontology_version: str | None = None
136
+ action: str
137
+ object_type: str
138
+ effect: str
139
+ params: dict[str, Any]
140
+ key: dict[str, Any] | None = None
141
+ sql: str | None = None
142
+ status: str
143
+ """``ok``, ``unchanged``, ``error``, ``denied``; for a tool write also
144
+ ``pending`` (being sent) and ``unknown`` (sent, but no answer said whether
145
+ it was done)."""
146
+ rows: int | None = None
147
+ elapsed_ms: int | None = None
148
+ error: str | None = None
149
+ tool: str | None = None
150
+ idempotency_key: str | None = None
151
+ steps: list[dict[str, Any]] | None = None
152
+ """For a tool write: each request's step, method, URL and status."""
153
+ at: datetime
@@ -0,0 +1,54 @@
1
+ """Agent runs: an agent action carried out by Nova, kept with what it did."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Any, Literal
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+ from nova_wire.actions import ActionResultOut
11
+ from nova_wire.snapshots import JobStatus
12
+
13
+ type AgentStepKind = Literal["query", "note", "write", "error"]
14
+
15
+
16
+ class AgentStepOut(BaseModel):
17
+ """One thing the agent did: a query it ran, what it said, the write it
18
+ asked for, or a call Nova turned down (which it then fixed)."""
19
+
20
+ kind: AgentStepKind
21
+ text: str | None = None
22
+ """For a note, what the model said; for an error, what was wrong."""
23
+ query: dict[str, Any] | None = None
24
+ """For a query, the query it ran (the ontology's IR)."""
25
+ rows: int | None = None
26
+ """For a query, how many rows came back."""
27
+ params: dict[str, Any] | None = None
28
+ """For a write, what it gave the action."""
29
+ elapsed_ms: int | None = None
30
+
31
+
32
+ class AgentRunOut(BaseModel):
33
+ id: str
34
+ action: str
35
+ key: str
36
+ """The primary key of the object it read."""
37
+ status: JobStatus
38
+ trigger: Literal["person", "created"]
39
+ """``person``: someone ran the action; ``created``: a new object of its
40
+ type started it."""
41
+ requested_by: str
42
+ """Who ran the action, or created the object; ``nova:inbox`` for a message
43
+ the inbox received."""
44
+ model: str | None = None
45
+ files: list[str] = Field(default_factory=list)
46
+ """The names of the files it was shown."""
47
+ steps: list[AgentStepOut] | None = None
48
+ """What it did, in order; ``None`` for a caller whose role may not read
49
+ the object's confidential properties."""
50
+ written: ActionResultOut | None = None
51
+ """The write it ended with, once done."""
52
+ error: str | None = None
53
+ created_at: datetime
54
+ finished_at: datetime | None = None
@@ -0,0 +1,253 @@
1
+ """Registered apps (code projects from ``nova create-app``) and their hosted
2
+ sites."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from datetime import datetime
7
+ from typing import Any, Literal
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class AppRegister(BaseModel):
13
+ slug: str = Field(pattern=r"^[a-z0-9][a-z0-9-]{1,62}$")
14
+ name: str = Field(min_length=1, max_length=200)
15
+ repo_url: str | None = None
16
+ path: str | None = None
17
+ """The app's folder in its repo: ``apps/<slug>/``, or ``/`` for the repo's
18
+ root. Unset: ``apps/<slug>/``."""
19
+
20
+
21
+ class AppUpdate(BaseModel):
22
+ repo_url: str | None = None
23
+ """Where the app's code lives: a GitHub repository URL; null clears it."""
24
+ path: str | None = None
25
+ """The app's folder in that repo (``apps/<slug>/``, ``/`` for its root),
26
+ where Code and agents work on it; null puts it back to ``apps/<slug>/``."""
27
+ model_token_budget: int | None = Field(default=None, ge=1)
28
+ """Tokens (input and output) the app may spend through the model gateway
29
+ each calendar month; null lifts the limit."""
30
+
31
+
32
+ class AppOut(BaseModel):
33
+ id: str
34
+ slug: str
35
+ name: str
36
+ repo_url: str | None
37
+ path: str = ""
38
+ """The app's folder in its repo: ``apps/<slug>/``, or ``/`` for the root."""
39
+ token_prefix: str | None
40
+ created_at: datetime
41
+ site_url: str | None = None
42
+ """Where the hosted site's production is served, once a version is there."""
43
+ staging_version: str | None = None
44
+ production_version: str | None = None
45
+ model_token_budget: int | None = None
46
+ """Tokens the app may spend through the model gateway each calendar
47
+ month; null means no limit."""
48
+
49
+
50
+ class AppRegistered(AppOut):
51
+ token: str
52
+ """The app's own API token, shown once."""
53
+
54
+
55
+ # --- hosted sites ---------------------------------------------------------------------
56
+ #
57
+ # An app's frontend build is uploaded as a zip of its dist folder and kept as a
58
+ # version. The site has two environments, staging and production, each serving
59
+ # one released version at ``/sites/{org}/{slug}/`` (production by default;
60
+ # ``?env=staging`` switches a browser to staging, ``?version=`` previews any
61
+ # version). Limits match what a static host wants: 1,000 files and 20 MB unpacked.
62
+
63
+ SITE_MAX_FILES = 1000
64
+ SITE_MAX_BYTES = 20 * 1024 * 1024
65
+ SITE_VERSION_PATTERN = r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
66
+ """A site version's name: what `nova site deploy` uploads and ``?version=`` names."""
67
+
68
+ SiteEnvironment = Literal["staging", "production"]
69
+ SITE_ENVIRONMENTS: tuple[SiteEnvironment, ...] = ("staging", "production")
70
+ """In pipeline order: a version goes to staging, then production."""
71
+
72
+
73
+ class SiteVersionOut(BaseModel):
74
+ id: str
75
+ version: str
76
+ files: int
77
+ bytes: int
78
+ uploaded_by: str | None
79
+ notes: str | None
80
+ environments: list[SiteEnvironment]
81
+ """Where this version is released; empty when only uploaded."""
82
+ backend: bool = False
83
+ """Whether the app's backend was uploaded with it, to run on the platform at
84
+ ``/sites/{org}/{slug}/api/``."""
85
+ created_at: datetime
86
+ preview_url: str
87
+
88
+
89
+ class SiteReleaseOut(BaseModel):
90
+ """One release of a version to an environment, newest first in ``SiteOut``."""
91
+
92
+ id: str
93
+ environment: SiteEnvironment
94
+ version: str
95
+ released_by: str | None
96
+ notes: str | None
97
+ at: datetime
98
+ current: bool
99
+ """Still what the environment serves."""
100
+
101
+
102
+ class SiteOut(BaseModel):
103
+ app: str
104
+ url: str
105
+ """Where production is served (404 until a version is released there)."""
106
+ staging: SiteVersionOut | None
107
+ production: SiteVersionOut | None
108
+ versions: list[SiteVersionOut]
109
+ """Newest first."""
110
+ releases: list[SiteReleaseOut]
111
+ """Newest first."""
112
+
113
+
114
+ class SiteRelease(BaseModel):
115
+ """Serve ``version`` in ``environment`` from now on."""
116
+
117
+ version: str
118
+ environment: SiteEnvironment = "production"
119
+
120
+
121
+ BACKEND_MAX_BYTES = 50 * 1024 * 1024
122
+ """The largest backend archive (source and lock file, no virtualenv)."""
123
+
124
+
125
+ class FunctionEnvSet(BaseModel):
126
+ """Settings the hosted backend runs with (``nova site env set``), by name.
127
+ Values are kept encrypted and never shown again."""
128
+
129
+ values: dict[str, str] = Field(min_length=1)
130
+
131
+
132
+ class FunctionEnvOut(BaseModel):
133
+ keys: list[str]
134
+ """The names set, in order."""
135
+ builtin: list[str]
136
+ """What Nova sets by itself: the API's URL, the app's token and names."""
137
+
138
+
139
+ # --- an app's own object types -------------------------------------------------------
140
+
141
+
142
+ class AppOntologyPublish(BaseModel):
143
+ """``apps/<slug>/ontology.yaml``: object types (and links, groups) the app adds
144
+ over its own warehouse tables or views. Links may name the organization's types."""
145
+
146
+ spec_yaml: str
147
+ force: bool = False
148
+ """Publish despite validation errors."""
149
+
150
+
151
+ class AppOntologyCheck(BaseModel):
152
+ """``nova app ontology validate``: the fragment checked, nothing stored."""
153
+
154
+ valid: bool
155
+ org_version: str
156
+ snapshot_id: str | None
157
+ object_types: list[str]
158
+ link_types: list[str]
159
+ managed: list[str] = Field(default_factory=list)
160
+ """The object types whose tables Nova manages."""
161
+ plan: list[str] = Field(default_factory=list)
162
+ """The statements a publish would run on the managed database."""
163
+ errors: list[str]
164
+ warnings: list[str]
165
+
166
+
167
+ class EffectiveOntologyOut(BaseModel):
168
+ """Everything an app's sessions read, merged into one spec."""
169
+
170
+ version: str
171
+ """``<org version>+<fragment version>``, or the organization's alone."""
172
+ spec: dict[str, Any]
173
+ """The merged ontology in the spec's own shape (``ontology.yaml`` as JSON)."""
174
+
175
+
176
+ class AppOntologyOut(BaseModel):
177
+ app: str
178
+ version: str
179
+ """Content version of the fragment."""
180
+ org_version: str
181
+ """The organization's version it was merged with and checked against."""
182
+ snapshot_id: str | None
183
+ object_types: list[str]
184
+ link_types: list[str]
185
+ groups: list[str]
186
+ inputs: dict[str, list[str]]
187
+ """Per local table that is a warehouse view: the tables its definition reads."""
188
+ managed: list[str] = Field(default_factory=list)
189
+ """The object types whose tables Nova manages."""
190
+ migration: dict[str, list[str]] | None = None
191
+ """What this publish did to the managed database: the statements run, and
192
+ the types created, altered and unchanged."""
193
+ errors: list[str]
194
+ warnings: list[str]
195
+ published_by: str | None
196
+ published_at: datetime
197
+
198
+
199
+ # --- an app's card ------------------------------------------------------------------
200
+
201
+
202
+ class AppCardPublish(BaseModel):
203
+ """``apps/<slug>/card.yaml``: what the All Apps page shows for the app."""
204
+
205
+ spec_yaml: str
206
+ force: bool = False
207
+ """Publish despite validation errors."""
208
+
209
+
210
+ class AppCardCheck(BaseModel):
211
+ """``nova app card validate``: the card checked, nothing stored."""
212
+
213
+ valid: bool
214
+ org_version: str | None
215
+ queries: list[str]
216
+ """Where the card runs a query: ``metric``, ``metric.delta``, ``trend``."""
217
+ errors: list[str]
218
+ warnings: list[str]
219
+
220
+
221
+ class CardMetricData(BaseModel):
222
+ value: str
223
+ raw: float | str | None = None
224
+ delta: str | None = None
225
+ delta_tone: Literal["ok", "warn", "crit", "muted"] | None = None
226
+
227
+
228
+ class CardTrendData(BaseModel):
229
+ values: list[float]
230
+ labels: list[str]
231
+
232
+
233
+ class CardData(BaseModel):
234
+ """What the card's queries answered when they last ran."""
235
+
236
+ metric: CardMetricData | None = None
237
+ trend: CardTrendData | None = None
238
+ refreshed_at: datetime | None = None
239
+ elapsed_ms: int | None = None
240
+ errors: list[str] = Field(default_factory=list)
241
+
242
+
243
+ class AppCardOut(BaseModel):
244
+ app: str
245
+ version: str
246
+ """Content version of the card."""
247
+ spec: dict[str, Any]
248
+ """The card as published, in the shape of ``card.yaml``."""
249
+ data: CardData
250
+ errors: list[str]
251
+ warnings: list[str]
252
+ published_by: str | None
253
+ published_at: datetime