spaps 0.7.3 → 0.7.5
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/AI_TOOLS.json +10 -11
- package/README.md +216 -36
- package/assets/local-runtime/Dockerfile +28 -0
- package/assets/local-runtime/alembic/env.py +101 -0
- package/assets/local-runtime/alembic/path_bootstrap.py +71 -0
- package/assets/local-runtime/alembic/versions/000000000001_baseline_consolidated_schema.py +1076 -0
- package/assets/local-runtime/alembic/versions/000000000002_fix_column_types_to_match_prod.py +83 -0
- package/assets/local-runtime/alembic/versions/000000000003_fix_email_template_key_uniqueness.py +49 -0
- package/assets/local-runtime/alembic/versions/000000000004_add_hold_duration_minutes_to_dayrate_config.py +30 -0
- package/assets/local-runtime/alembic/versions/000000000005_resource_scoped_entitlements.py +77 -0
- package/assets/local-runtime/alembic/versions/000000000006_cfo_rbac_add_is_admin.py +37 -0
- package/assets/local-runtime/alembic/versions/000000000007_agent_approvals.py +158 -0
- package/assets/local-runtime/alembic/versions/000000000008_add_company_id_to_cfo_connections.py +35 -0
- package/assets/local-runtime/alembic/versions/000000000009_tx_signing.py +62 -0
- package/assets/local-runtime/alembic/versions/000000000010_affiliate_referrals.py +235 -0
- package/assets/local-runtime/alembic/versions/000000000011_checkin_call_booking.py +137 -0
- package/assets/local-runtime/alembic/versions/000000000012_subscription_application_scoping.py +55 -0
- package/assets/local-runtime/alembic/versions/000000000013_refresh_token_anomaly_context.py +61 -0
- package/assets/local-runtime/alembic/versions/000000000014_buildooor_dayrate_hire_schedule.py +39 -0
- package/assets/local-runtime/alembic/versions/000000000015_support_telemetry_platform.py +112 -0
- package/assets/local-runtime/alembic/versions/000000000016_issue_reporting_platform.py +54 -0
- package/assets/local-runtime/alembic/versions/000000000017_issue_reporting_platform_import_tracking.py +44 -0
- package/assets/local-runtime/alembic/versions/000000000018_authorization_policy_engine.py +76 -0
- package/assets/local-runtime/alembic.ini +47 -0
- package/assets/local-runtime/docker-compose.yml +61 -0
- package/assets/local-runtime/manifest.json +8 -0
- package/assets/local-runtime/scripts/container-entrypoint.sh +13 -0
- package/assets/local-runtime/scripts/fetch-prod-db.sh +112 -0
- package/assets/local-runtime/scripts/run-migrations.sh +96 -0
- package/package.json +2 -1
- package/src/ai-helper.js +176 -234
- package/src/ai-tool-spec.js +52 -20
- package/src/auth/api-key.js +119 -0
- package/src/auth/client-id.js +136 -0
- package/src/auth/client.js +169 -0
- package/src/auth/credentials.js +110 -0
- package/src/auth/device-flow.js +159 -0
- package/src/auth/env.js +57 -0
- package/src/auth/handlers.js +462 -0
- package/src/auth/http.js +74 -0
- package/src/cli-dispatcher.js +134 -21
- package/src/docs-system.js +7 -7
- package/src/fixture-kernel.js +1143 -0
- package/src/handlers.js +202 -11
- package/src/help-system.js +2 -0
- package/src/local-runtime.js +258 -0
- package/src/local-server.js +597 -199
- package/src/project-scaffolder.js +185 -45
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""fix column types to match production
|
|
2
|
+
|
|
3
|
+
Revision ID: 000000000002
|
|
4
|
+
Revises: 000000000001
|
|
5
|
+
Create Date: 2026-02-09
|
|
6
|
+
|
|
7
|
+
Fix column types that diverged from the production Supabase schema:
|
|
8
|
+
- dayrate_config.slots: JSONB → TEXT[] (was always a text array in prod)
|
|
9
|
+
- endpoint_documentation.authentication: JSONB → TEXT (was always plain text in prod)
|
|
10
|
+
- endpoint_documentation.error_codes: JSONB → JSONB[] (was always a jsonb array in prod)
|
|
11
|
+
|
|
12
|
+
Idempotent: skips columns that already have the correct type (e.g. fresh installs
|
|
13
|
+
where the baseline was fixed).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from alembic import op
|
|
17
|
+
|
|
18
|
+
revision = "000000000002"
|
|
19
|
+
down_revision = "000000000001"
|
|
20
|
+
branch_labels = None
|
|
21
|
+
depends_on = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def upgrade() -> None:
|
|
25
|
+
# Each block checks current type and only alters if wrong.
|
|
26
|
+
# On fresh installs the baseline already has the correct types.
|
|
27
|
+
|
|
28
|
+
op.execute("""
|
|
29
|
+
DO $$ BEGIN
|
|
30
|
+
IF (SELECT udt_name FROM information_schema.columns
|
|
31
|
+
WHERE table_schema = 'public'
|
|
32
|
+
AND table_name = 'dayrate_config'
|
|
33
|
+
AND column_name = 'slots') = 'jsonb' THEN
|
|
34
|
+
ALTER TABLE dayrate_config
|
|
35
|
+
ALTER COLUMN slots TYPE TEXT[] USING '{}';
|
|
36
|
+
END IF;
|
|
37
|
+
END $$
|
|
38
|
+
""")
|
|
39
|
+
|
|
40
|
+
op.execute("""
|
|
41
|
+
DO $$ BEGIN
|
|
42
|
+
IF (SELECT udt_name FROM information_schema.columns
|
|
43
|
+
WHERE table_schema = 'public'
|
|
44
|
+
AND table_name = 'endpoint_documentation'
|
|
45
|
+
AND column_name = 'authentication') = 'jsonb' THEN
|
|
46
|
+
ALTER TABLE endpoint_documentation
|
|
47
|
+
ALTER COLUMN authentication TYPE TEXT
|
|
48
|
+
USING authentication::TEXT;
|
|
49
|
+
END IF;
|
|
50
|
+
END $$
|
|
51
|
+
""")
|
|
52
|
+
|
|
53
|
+
op.execute("""
|
|
54
|
+
DO $$ BEGIN
|
|
55
|
+
IF (SELECT udt_name FROM information_schema.columns
|
|
56
|
+
WHERE table_schema = 'public'
|
|
57
|
+
AND table_name = 'endpoint_documentation'
|
|
58
|
+
AND column_name = 'error_codes') = 'jsonb' THEN
|
|
59
|
+
ALTER TABLE endpoint_documentation
|
|
60
|
+
ALTER COLUMN error_codes TYPE JSONB[]
|
|
61
|
+
USING ARRAY[]::JSONB[];
|
|
62
|
+
END IF;
|
|
63
|
+
END $$
|
|
64
|
+
""")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def downgrade() -> None:
|
|
68
|
+
op.execute("""
|
|
69
|
+
ALTER TABLE dayrate_config
|
|
70
|
+
ALTER COLUMN slots TYPE JSONB USING to_jsonb(slots)
|
|
71
|
+
""")
|
|
72
|
+
|
|
73
|
+
op.execute("""
|
|
74
|
+
ALTER TABLE endpoint_documentation
|
|
75
|
+
ALTER COLUMN authentication TYPE JSONB
|
|
76
|
+
USING to_jsonb(authentication)
|
|
77
|
+
""")
|
|
78
|
+
|
|
79
|
+
op.execute("""
|
|
80
|
+
ALTER TABLE endpoint_documentation
|
|
81
|
+
ALTER COLUMN error_codes TYPE JSONB
|
|
82
|
+
USING to_jsonb(error_codes)
|
|
83
|
+
""")
|
package/assets/local-runtime/alembic/versions/000000000003_fix_email_template_key_uniqueness.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""fix email template_key uniqueness to composite (application_id, template_key)
|
|
2
|
+
|
|
3
|
+
Revision ID: 000000000003
|
|
4
|
+
Revises: 000000000002
|
|
5
|
+
Create Date: 2026-02-09
|
|
6
|
+
|
|
7
|
+
The template_key column had a global unique constraint, preventing
|
|
8
|
+
multi-tenant use (e.g. two different applications both creating
|
|
9
|
+
"auth_magic_link"). This migration replaces the single-column unique
|
|
10
|
+
constraint with a composite unique index on (application_id, template_key).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from alembic import op
|
|
14
|
+
|
|
15
|
+
revision = "000000000003"
|
|
16
|
+
down_revision = "000000000002"
|
|
17
|
+
branch_labels = None
|
|
18
|
+
depends_on = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
# Drop the single-column unique constraint if it exists
|
|
23
|
+
op.execute("""
|
|
24
|
+
ALTER TABLE email_templates
|
|
25
|
+
DROP CONSTRAINT IF EXISTS email_templates_template_key_key;
|
|
26
|
+
""")
|
|
27
|
+
|
|
28
|
+
# Also drop the index-based form if present
|
|
29
|
+
op.execute("""
|
|
30
|
+
DROP INDEX IF EXISTS ix_email_templates_template_key;
|
|
31
|
+
""")
|
|
32
|
+
|
|
33
|
+
# Create composite unique index
|
|
34
|
+
op.execute("""
|
|
35
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_email_templates_app_key
|
|
36
|
+
ON email_templates (application_id, template_key);
|
|
37
|
+
""")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def downgrade() -> None:
|
|
41
|
+
op.execute("""
|
|
42
|
+
DROP INDEX IF EXISTS uq_email_templates_app_key;
|
|
43
|
+
""")
|
|
44
|
+
|
|
45
|
+
# Restore original single-column unique constraint
|
|
46
|
+
op.execute("""
|
|
47
|
+
ALTER TABLE email_templates
|
|
48
|
+
ADD CONSTRAINT email_templates_template_key_key UNIQUE (template_key);
|
|
49
|
+
""")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""add hold_duration_minutes to dayrate_config
|
|
2
|
+
|
|
3
|
+
Revision ID: 000000000004
|
|
4
|
+
Revises: 000000000003
|
|
5
|
+
Create Date: 2026-02-11
|
|
6
|
+
|
|
7
|
+
Adds a configurable hold duration (in minutes) for pending dayrate bookings.
|
|
8
|
+
Previously hardcoded to 30 minutes. Defaults to 30 for backwards compatibility.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from alembic import op
|
|
12
|
+
|
|
13
|
+
revision = "000000000004"
|
|
14
|
+
down_revision = "000000000003"
|
|
15
|
+
branch_labels = None
|
|
16
|
+
depends_on = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
op.execute("""
|
|
21
|
+
ALTER TABLE dayrate_config
|
|
22
|
+
ADD COLUMN IF NOT EXISTS hold_duration_minutes INTEGER NOT NULL DEFAULT 30;
|
|
23
|
+
""")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def downgrade() -> None:
|
|
27
|
+
op.execute("""
|
|
28
|
+
ALTER TABLE dayrate_config
|
|
29
|
+
DROP COLUMN IF EXISTS hold_duration_minutes;
|
|
30
|
+
""")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""resource scoped entitlements
|
|
2
|
+
|
|
3
|
+
Revision ID: 000000000005
|
|
4
|
+
Revises: 000000000004
|
|
5
|
+
Create Date: 2026-02-11
|
|
6
|
+
|
|
7
|
+
Adds resource_type and resource_id columns to the entitlements table
|
|
8
|
+
to support company, org, and system-scoped entitlements in addition
|
|
9
|
+
to user-scoped ones.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from alembic import op
|
|
13
|
+
|
|
14
|
+
revision = "000000000005"
|
|
15
|
+
down_revision = "000000000004"
|
|
16
|
+
branch_labels = None
|
|
17
|
+
depends_on = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade() -> None:
|
|
21
|
+
# Add resource_type column with default 'user'
|
|
22
|
+
op.execute("""
|
|
23
|
+
ALTER TABLE entitlements
|
|
24
|
+
ADD COLUMN IF NOT EXISTS resource_type VARCHAR NOT NULL DEFAULT 'user';
|
|
25
|
+
""")
|
|
26
|
+
|
|
27
|
+
# Add CHECK constraint for allowed resource_type values
|
|
28
|
+
op.execute("""
|
|
29
|
+
ALTER TABLE entitlements
|
|
30
|
+
ADD CONSTRAINT ck_entitlements_resource_type
|
|
31
|
+
CHECK (resource_type IN ('user', 'company', 'system', 'org'));
|
|
32
|
+
""")
|
|
33
|
+
|
|
34
|
+
# Add resource_id column (nullable UUID)
|
|
35
|
+
op.execute("""
|
|
36
|
+
ALTER TABLE entitlements
|
|
37
|
+
ADD COLUMN IF NOT EXISTS resource_id UUID;
|
|
38
|
+
""")
|
|
39
|
+
|
|
40
|
+
# Add CHECK constraint: resource_id required for company and org types
|
|
41
|
+
op.execute("""
|
|
42
|
+
ALTER TABLE entitlements
|
|
43
|
+
ADD CONSTRAINT ck_entitlements_resource_id_required
|
|
44
|
+
CHECK (
|
|
45
|
+
resource_type NOT IN ('company', 'org')
|
|
46
|
+
OR resource_id IS NOT NULL
|
|
47
|
+
);
|
|
48
|
+
""")
|
|
49
|
+
|
|
50
|
+
# Add partial index for resource lookups
|
|
51
|
+
op.execute("""
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_entitlements_resource
|
|
53
|
+
ON entitlements (resource_type, resource_id)
|
|
54
|
+
WHERE resource_id IS NOT NULL;
|
|
55
|
+
""")
|
|
56
|
+
|
|
57
|
+
# Add partial unique index for non-user resource scoping
|
|
58
|
+
op.execute("""
|
|
59
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_entitlements_resource_unique
|
|
60
|
+
ON entitlements (application_id, entitlement_key, resource_type, resource_id, source)
|
|
61
|
+
WHERE resource_type != 'user';
|
|
62
|
+
""")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def downgrade() -> None:
|
|
66
|
+
op.execute("DROP INDEX IF EXISTS idx_entitlements_resource_unique;")
|
|
67
|
+
op.execute("DROP INDEX IF EXISTS idx_entitlements_resource;")
|
|
68
|
+
op.execute("""
|
|
69
|
+
ALTER TABLE entitlements
|
|
70
|
+
DROP CONSTRAINT IF EXISTS ck_entitlements_resource_id_required;
|
|
71
|
+
""")
|
|
72
|
+
op.execute("""
|
|
73
|
+
ALTER TABLE entitlements
|
|
74
|
+
DROP CONSTRAINT IF EXISTS ck_entitlements_resource_type;
|
|
75
|
+
""")
|
|
76
|
+
op.execute("ALTER TABLE entitlements DROP COLUMN IF EXISTS resource_id;")
|
|
77
|
+
op.execute("ALTER TABLE entitlements DROP COLUMN IF EXISTS resource_type;")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""CFO RBAC: add is_admin to users table
|
|
2
|
+
|
|
3
|
+
Revision ID: 000000000006
|
|
4
|
+
Revises: 000000000005
|
|
5
|
+
Create Date: 2026-02-11
|
|
6
|
+
|
|
7
|
+
Adds is_admin column to users table (formerly user_profiles in the legacy
|
|
8
|
+
Node.js stack, merged into users in the Python rewrite). This provides
|
|
9
|
+
a fast-path admin check for CFO-specific routes without joining
|
|
10
|
+
user_applications.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from alembic import op
|
|
14
|
+
|
|
15
|
+
revision = "000000000006"
|
|
16
|
+
down_revision = "000000000005"
|
|
17
|
+
branch_labels = None
|
|
18
|
+
depends_on = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
op.execute("""
|
|
23
|
+
ALTER TABLE users
|
|
24
|
+
ADD COLUMN IF NOT EXISTS is_admin BOOLEAN NOT NULL DEFAULT false;
|
|
25
|
+
""")
|
|
26
|
+
|
|
27
|
+
# Index for quick admin lookups
|
|
28
|
+
op.execute("""
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_users_is_admin
|
|
30
|
+
ON users (is_admin)
|
|
31
|
+
WHERE is_admin = true;
|
|
32
|
+
""")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def downgrade() -> None:
|
|
36
|
+
op.execute("DROP INDEX IF EXISTS idx_users_is_admin;")
|
|
37
|
+
op.execute("ALTER TABLE users DROP COLUMN IF EXISTS is_admin;")
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""agent approvals
|
|
2
|
+
|
|
3
|
+
Revision ID: 000000000007
|
|
4
|
+
Revises: 000000000006
|
|
5
|
+
Create Date: 2026-02-11
|
|
6
|
+
|
|
7
|
+
Creates agents, agent_approvers, and agent_approvals tables for the
|
|
8
|
+
agent consent gateway feature.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from alembic import op
|
|
12
|
+
|
|
13
|
+
revision = "000000000007"
|
|
14
|
+
down_revision = "000000000006"
|
|
15
|
+
branch_labels = None
|
|
16
|
+
depends_on = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
# -----------------------------------------------------------------------
|
|
21
|
+
# agents table
|
|
22
|
+
# -----------------------------------------------------------------------
|
|
23
|
+
op.execute("""
|
|
24
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
25
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
26
|
+
application_id UUID NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
|
|
27
|
+
owner_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
28
|
+
name VARCHAR(100) NOT NULL,
|
|
29
|
+
description VARCHAR(500),
|
|
30
|
+
agent_secret_hash VARCHAR(128) NOT NULL,
|
|
31
|
+
agent_secret_prefix VARCHAR(8) NOT NULL,
|
|
32
|
+
scopes JSONB DEFAULT '[]'::jsonb,
|
|
33
|
+
metadata JSONB DEFAULT '{}'::jsonb,
|
|
34
|
+
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
35
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
36
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
37
|
+
);
|
|
38
|
+
""")
|
|
39
|
+
|
|
40
|
+
# Unique name per application (active agents only)
|
|
41
|
+
op.execute("""
|
|
42
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_agents_app_name_active
|
|
43
|
+
ON agents (application_id, name)
|
|
44
|
+
WHERE is_active = true;
|
|
45
|
+
""")
|
|
46
|
+
|
|
47
|
+
# Lookup indexes
|
|
48
|
+
op.execute("""
|
|
49
|
+
CREATE INDEX IF NOT EXISTS ix_agents_app_owner
|
|
50
|
+
ON agents (application_id, owner_user_id);
|
|
51
|
+
""")
|
|
52
|
+
op.execute("""
|
|
53
|
+
CREATE INDEX IF NOT EXISTS ix_agents_owner
|
|
54
|
+
ON agents (owner_user_id);
|
|
55
|
+
""")
|
|
56
|
+
|
|
57
|
+
# -----------------------------------------------------------------------
|
|
58
|
+
# agent_approvers table
|
|
59
|
+
# -----------------------------------------------------------------------
|
|
60
|
+
op.execute("""
|
|
61
|
+
CREATE TABLE IF NOT EXISTS agent_approvers (
|
|
62
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
63
|
+
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
64
|
+
approver_user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
65
|
+
approver_agent_id UUID REFERENCES agents(id) ON DELETE CASCADE,
|
|
66
|
+
approver_type VARCHAR(10) NOT NULL,
|
|
67
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
68
|
+
|
|
69
|
+
CONSTRAINT ck_agent_approvers_type
|
|
70
|
+
CHECK (approver_type IN ('user', 'agent')),
|
|
71
|
+
|
|
72
|
+
CONSTRAINT ck_agent_approvers_exactly_one_fk
|
|
73
|
+
CHECK (
|
|
74
|
+
(approver_type = 'user' AND approver_user_id IS NOT NULL AND approver_agent_id IS NULL)
|
|
75
|
+
OR (approver_type = 'agent' AND approver_agent_id IS NOT NULL AND approver_user_id IS NULL)
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
""")
|
|
79
|
+
|
|
80
|
+
# Unique approver per agent (user)
|
|
81
|
+
op.execute("""
|
|
82
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_agent_approvers_user
|
|
83
|
+
ON agent_approvers (agent_id, approver_user_id)
|
|
84
|
+
WHERE approver_type = 'user';
|
|
85
|
+
""")
|
|
86
|
+
|
|
87
|
+
# Unique approver per agent (agent)
|
|
88
|
+
op.execute("""
|
|
89
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_agent_approvers_agent
|
|
90
|
+
ON agent_approvers (agent_id, approver_agent_id)
|
|
91
|
+
WHERE approver_type = 'agent';
|
|
92
|
+
""")
|
|
93
|
+
|
|
94
|
+
# Lookup indexes
|
|
95
|
+
op.execute("""
|
|
96
|
+
CREATE INDEX IF NOT EXISTS ix_agent_approvers_agent
|
|
97
|
+
ON agent_approvers (agent_id);
|
|
98
|
+
""")
|
|
99
|
+
op.execute("""
|
|
100
|
+
CREATE INDEX IF NOT EXISTS ix_agent_approvers_user
|
|
101
|
+
ON agent_approvers (approver_user_id)
|
|
102
|
+
WHERE approver_user_id IS NOT NULL;
|
|
103
|
+
""")
|
|
104
|
+
op.execute("""
|
|
105
|
+
CREATE INDEX IF NOT EXISTS ix_agent_approvers_agent_id
|
|
106
|
+
ON agent_approvers (approver_agent_id)
|
|
107
|
+
WHERE approver_agent_id IS NOT NULL;
|
|
108
|
+
""")
|
|
109
|
+
|
|
110
|
+
# -----------------------------------------------------------------------
|
|
111
|
+
# agent_approvals table
|
|
112
|
+
# -----------------------------------------------------------------------
|
|
113
|
+
op.execute("""
|
|
114
|
+
CREATE TABLE IF NOT EXISTS agent_approvals (
|
|
115
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
116
|
+
application_id UUID NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
|
|
117
|
+
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
118
|
+
code VARCHAR(9) NOT NULL UNIQUE,
|
|
119
|
+
action VARCHAR(200) NOT NULL,
|
|
120
|
+
resource_type VARCHAR(100),
|
|
121
|
+
resource_id VARCHAR(500),
|
|
122
|
+
reason VARCHAR(1000),
|
|
123
|
+
metadata JSONB DEFAULT '{}'::jsonb,
|
|
124
|
+
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
125
|
+
callback_url VARCHAR(2048),
|
|
126
|
+
note VARCHAR(500),
|
|
127
|
+
responded_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
128
|
+
responded_by_agent_id UUID REFERENCES agents(id) ON DELETE SET NULL,
|
|
129
|
+
responder_type VARCHAR(10),
|
|
130
|
+
responded_at TIMESTAMPTZ,
|
|
131
|
+
approval_token TEXT,
|
|
132
|
+
token_expires_at TIMESTAMPTZ,
|
|
133
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
134
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
135
|
+
|
|
136
|
+
CONSTRAINT ck_agent_approvals_status
|
|
137
|
+
CHECK (status IN ('pending', 'approved', 'denied', 'expired'))
|
|
138
|
+
);
|
|
139
|
+
""")
|
|
140
|
+
|
|
141
|
+
# Composite index for agent polling
|
|
142
|
+
op.execute("""
|
|
143
|
+
CREATE INDEX IF NOT EXISTS ix_agent_approvals_app_agent_status
|
|
144
|
+
ON agent_approvals (application_id, agent_id, status);
|
|
145
|
+
""")
|
|
146
|
+
|
|
147
|
+
# Partial index for expiration cleanup
|
|
148
|
+
op.execute("""
|
|
149
|
+
CREATE INDEX IF NOT EXISTS ix_agent_approvals_pending_expiry
|
|
150
|
+
ON agent_approvals (status, expires_at)
|
|
151
|
+
WHERE status = 'pending';
|
|
152
|
+
""")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def downgrade() -> None:
|
|
156
|
+
op.execute("DROP TABLE IF EXISTS agent_approvals CASCADE;")
|
|
157
|
+
op.execute("DROP TABLE IF EXISTS agent_approvers CASCADE;")
|
|
158
|
+
op.execute("DROP TABLE IF EXISTS agents CASCADE;")
|
package/assets/local-runtime/alembic/versions/000000000008_add_company_id_to_cfo_connections.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""add company_id to cfo_connections
|
|
2
|
+
|
|
3
|
+
Revision ID: 000000000008
|
|
4
|
+
Revises: 000000000007
|
|
5
|
+
Create Date: 2026-02-12
|
|
6
|
+
|
|
7
|
+
Adds a nullable company_id UUID column to the cfo_connections table.
|
|
8
|
+
This is a soft reference to cfo_companies.id in the CFO database.
|
|
9
|
+
No foreign key constraint — the reference is cross-database.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from alembic import op
|
|
13
|
+
|
|
14
|
+
revision = "000000000008"
|
|
15
|
+
down_revision = "000000000007"
|
|
16
|
+
branch_labels = None
|
|
17
|
+
depends_on = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade() -> None:
|
|
21
|
+
op.execute("""
|
|
22
|
+
ALTER TABLE cfo_connections
|
|
23
|
+
ADD COLUMN IF NOT EXISTS company_id UUID NULL;
|
|
24
|
+
""")
|
|
25
|
+
op.execute("""
|
|
26
|
+
COMMENT ON COLUMN cfo_connections.company_id
|
|
27
|
+
IS 'Soft reference to cfo_companies.id in CFO DB (no FK constraint)';
|
|
28
|
+
""")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def downgrade() -> None:
|
|
32
|
+
op.execute("""
|
|
33
|
+
ALTER TABLE cfo_connections
|
|
34
|
+
DROP COLUMN IF EXISTS company_id;
|
|
35
|
+
""")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Add transaction signing fields to agent_approvals
|
|
2
|
+
|
|
3
|
+
Revision ID: 000000000009
|
|
4
|
+
Revises: 000000000008
|
|
5
|
+
Create Date: 2026-02-13
|
|
6
|
+
|
|
7
|
+
Add 4 nullable columns to agent_approvals to support transaction intent
|
|
8
|
+
and wallet signing (tx_signing):
|
|
9
|
+
- transaction_intent: JSONB storing EVM/Solana transaction intent
|
|
10
|
+
- signed_transaction: TEXT storing hex/base64 signed transaction bytes
|
|
11
|
+
- signer_address: TEXT storing wallet address that signed
|
|
12
|
+
- edited_intent: JSONB storing modified intent if approver edited
|
|
13
|
+
|
|
14
|
+
All columns are nullable for backward compatibility.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from alembic import op
|
|
18
|
+
|
|
19
|
+
revision = "000000000009"
|
|
20
|
+
down_revision = "000000000008"
|
|
21
|
+
branch_labels = None
|
|
22
|
+
depends_on = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def upgrade() -> None:
|
|
26
|
+
"""Add transaction signing fields to agent_approvals."""
|
|
27
|
+
op.execute("""
|
|
28
|
+
ALTER TABLE agent_approvals
|
|
29
|
+
ADD COLUMN transaction_intent JSONB;
|
|
30
|
+
""")
|
|
31
|
+
op.execute("""
|
|
32
|
+
ALTER TABLE agent_approvals
|
|
33
|
+
ADD COLUMN signed_transaction TEXT;
|
|
34
|
+
""")
|
|
35
|
+
op.execute("""
|
|
36
|
+
ALTER TABLE agent_approvals
|
|
37
|
+
ADD COLUMN signer_address VARCHAR(500);
|
|
38
|
+
""")
|
|
39
|
+
op.execute("""
|
|
40
|
+
ALTER TABLE agent_approvals
|
|
41
|
+
ADD COLUMN edited_intent JSONB;
|
|
42
|
+
""")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def downgrade() -> None:
|
|
46
|
+
"""Remove transaction signing fields from agent_approvals."""
|
|
47
|
+
op.execute("""
|
|
48
|
+
ALTER TABLE agent_approvals
|
|
49
|
+
DROP COLUMN IF EXISTS transaction_intent;
|
|
50
|
+
""")
|
|
51
|
+
op.execute("""
|
|
52
|
+
ALTER TABLE agent_approvals
|
|
53
|
+
DROP COLUMN IF EXISTS signed_transaction;
|
|
54
|
+
""")
|
|
55
|
+
op.execute("""
|
|
56
|
+
ALTER TABLE agent_approvals
|
|
57
|
+
DROP COLUMN IF EXISTS signer_address;
|
|
58
|
+
""")
|
|
59
|
+
op.execute("""
|
|
60
|
+
ALTER TABLE agent_approvals
|
|
61
|
+
DROP COLUMN IF EXISTS edited_intent;
|
|
62
|
+
""")
|