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.
- endeavor_nova_wire-0.1.0.dev394/LICENSE +21 -0
- endeavor_nova_wire-0.1.0.dev394/PKG-INFO +9 -0
- endeavor_nova_wire-0.1.0.dev394/pyproject.toml +18 -0
- endeavor_nova_wire-0.1.0.dev394/pyproject.toml.orig +18 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/__init__.py +4 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/access.py +105 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/actions.py +153 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/agents.py +54 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/apps.py +253 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/auth.py +78 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/base.py +26 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/connections.py +182 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/drafts.py +70 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/exports.py +48 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/extractions.py +84 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/files.py +35 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/inbox.py +89 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/model.py +289 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/ontology.py +145 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/orgs.py +61 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/py.typed +0 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/query.py +434 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/snapshots.py +55 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/telemetry.py +233 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/tools.py +175 -0
- endeavor_nova_wire-0.1.0.dev394/src/nova_wire/views.py +655 -0
- 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,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
|