Refactor code structure for improved readability and maintainability; removed redundant code blocks and optimized function calls.
This commit is contained in:
10
src/Api/StellaOps.Api.OpenApi/CHANGELOG.md
Normal file
10
src/Api/StellaOps.Api.OpenApi/CHANGELOG.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# API Changelog
|
||||
|
||||
Generated: 2025-11-19T07:40:32.086Z
|
||||
|
||||
## Additive Operations
|
||||
- GET /export-center/bundles/{bundleId}/manifest
|
||||
- GET /graph/graphs/{graphId}/nodes
|
||||
|
||||
## Breaking Operations
|
||||
- None
|
||||
17
src/Api/StellaOps.Api.OpenApi/_shared/parameters/paging.yaml
Normal file
17
src/Api/StellaOps.Api.OpenApi/_shared/parameters/paging.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
parameters:
|
||||
LimitParam:
|
||||
name: limit
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 200
|
||||
example: 50
|
||||
CursorParam:
|
||||
name: cursor
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
example: eyJyIjoiMjAyNS0xMS0xOC0wMDIifQ
|
||||
@@ -0,0 +1,9 @@
|
||||
parameters:
|
||||
TenantParam:
|
||||
name: tenant
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: Filter results to a specific tenant identifier.
|
||||
example: acme
|
||||
@@ -0,0 +1,13 @@
|
||||
responses:
|
||||
ErrorResponse:
|
||||
description: Error envelope
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../schemas/common.yaml#/schemas/ErrorEnvelope'
|
||||
HealthResponse:
|
||||
description: Health envelope
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../schemas/common.yaml#/schemas/HealthEnvelope'
|
||||
37
src/Api/StellaOps.Api.OpenApi/_shared/schemas/common.yaml
Normal file
37
src/Api/StellaOps.Api.OpenApi/_shared/schemas/common.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
schemas:
|
||||
ErrorEnvelope:
|
||||
type: object
|
||||
required: [code, message]
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: service_unavailable
|
||||
message:
|
||||
type: string
|
||||
traceId:
|
||||
type: string
|
||||
description: Correlation identifier for troubleshooting
|
||||
HealthEnvelope:
|
||||
type: object
|
||||
required: [status, service]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: ok
|
||||
service:
|
||||
type: string
|
||||
example: any-service
|
||||
PageMetadata:
|
||||
type: object
|
||||
required:
|
||||
- hasMore
|
||||
properties:
|
||||
hasMore:
|
||||
type: boolean
|
||||
description: Indicates if additional pages are available.
|
||||
nextCursor:
|
||||
type: string
|
||||
description: Cursor to fetch the next page.
|
||||
previousCursor:
|
||||
type: string
|
||||
description: Cursor to fetch the previous page.
|
||||
@@ -0,0 +1,12 @@
|
||||
securitySchemes:
|
||||
OAuthClientCredentials:
|
||||
type: oauth2
|
||||
description: OAuth 2.1 client credentials flow scoped per service.
|
||||
flows:
|
||||
clientCredentials:
|
||||
tokenUrl: /token
|
||||
scopes: {}
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
1377
src/Api/StellaOps.Api.OpenApi/baselines/stella-baseline.yaml
Normal file
1377
src/Api/StellaOps.Api.OpenApi/baselines/stella-baseline.yaml
Normal file
File diff suppressed because it is too large
Load Diff
205
src/Api/StellaOps.Api.OpenApi/compose.mjs
Normal file
205
src/Api/StellaOps.Api.OpenApi/compose.mjs
Normal file
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import yaml from 'yaml';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const ROOT = path.resolve(__dirname);
|
||||
const OUTPUT = path.join(ROOT, 'stella.yaml');
|
||||
const SHARED_COMPONENTS_DIR = path.join(ROOT, '_shared');
|
||||
const SHARED_SCHEMAS_DIR = path.join(SHARED_COMPONENTS_DIR, 'schemas');
|
||||
const SHARED_RESPONSES_DIR = path.join(SHARED_COMPONENTS_DIR, 'responses');
|
||||
const SHARED_PARAMETERS_DIR = path.join(SHARED_COMPONENTS_DIR, 'parameters');
|
||||
const SHARED_SECURITY_DIR = path.join(SHARED_COMPONENTS_DIR, 'securitySchemes');
|
||||
|
||||
function readServiceSpecs() {
|
||||
const entries = fs.readdirSync(ROOT, { withFileTypes: true });
|
||||
const services = [];
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const name = entry.name;
|
||||
if (name.startsWith('.')) continue;
|
||||
const specPath = path.join(ROOT, name, 'openapi.yaml');
|
||||
if (!fs.existsSync(specPath)) continue;
|
||||
const content = fs.readFileSync(specPath, 'utf8');
|
||||
const doc = yaml.parse(content, { prettyErrors: true });
|
||||
services.push({ name, specPath, doc });
|
||||
}
|
||||
services.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return services;
|
||||
}
|
||||
|
||||
function mergeSpecs(services) {
|
||||
const aggregate = {
|
||||
openapi: '3.1.0',
|
||||
info: {
|
||||
title: 'StellaOps Aggregate API',
|
||||
version: '0.0.1',
|
||||
description: 'Composed OpenAPI from per-service specs. This file is generated by compose.mjs.',
|
||||
},
|
||||
servers: [],
|
||||
paths: {},
|
||||
components: { schemas: {}, parameters: {}, securitySchemes: {}, responses: {} },
|
||||
};
|
||||
|
||||
// Shared components (schemas only for now)
|
||||
mergeShared(aggregate, SHARED_SCHEMAS_DIR, 'schemas');
|
||||
mergeShared(aggregate, SHARED_RESPONSES_DIR, 'responses');
|
||||
mergeShared(aggregate, SHARED_PARAMETERS_DIR, 'parameters');
|
||||
mergeShared(aggregate, SHARED_SECURITY_DIR, 'securitySchemes');
|
||||
|
||||
for (const { name, doc } of services) {
|
||||
// servers
|
||||
if (Array.isArray(doc.servers)) {
|
||||
for (const srv of doc.servers) {
|
||||
aggregate.servers.push({ ...srv, 'x-service': name });
|
||||
}
|
||||
}
|
||||
|
||||
// paths
|
||||
for (const [p, pathItem] of Object.entries(doc.paths || {})) {
|
||||
const namespacedPath = normalizePath(`/${name}${p}`);
|
||||
aggregate.paths[namespacedPath] ??= {};
|
||||
for (const [method, op] of Object.entries(pathItem || {})) {
|
||||
const lower = method.toLowerCase();
|
||||
if (!['get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace'].includes(lower)) continue;
|
||||
const opRewritten = rewriteRefs(op, name);
|
||||
const existing = aggregate.paths[namespacedPath][lower];
|
||||
if (existing) {
|
||||
throw new Error(`Path/method collision at ${namespacedPath} ${lower} (${p}) between services`);
|
||||
}
|
||||
aggregate.paths[namespacedPath][lower] = { ...opRewritten, 'x-service': name, 'x-original-path': p };
|
||||
}
|
||||
}
|
||||
|
||||
// schemas
|
||||
const schemas = doc.components?.schemas || {};
|
||||
for (const [schemaName, schemaDef] of Object.entries(schemas)) {
|
||||
const key = `${name}.${schemaName}`;
|
||||
if (aggregate.components.schemas[key]) {
|
||||
throw new Error(`Schema collision for ${key}`);
|
||||
}
|
||||
aggregate.components.schemas[key] = rewriteRefs(schemaDef, name);
|
||||
}
|
||||
}
|
||||
|
||||
// de-duplicate servers
|
||||
const seenServers = new Set();
|
||||
aggregate.servers = aggregate.servers.filter((srv) => {
|
||||
const key = JSON.stringify(srv);
|
||||
if (seenServers.has(key)) return false;
|
||||
seenServers.add(key);
|
||||
return true;
|
||||
});
|
||||
|
||||
aggregate.paths = sortObject(aggregate.paths, sortPathItem);
|
||||
aggregate.components.schemas = sortObject(aggregate.components.schemas);
|
||||
aggregate.components.responses = sortObject(aggregate.components.responses);
|
||||
aggregate.components.parameters = sortObject(aggregate.components.parameters);
|
||||
aggregate.components.securitySchemes = sortObject(aggregate.components.securitySchemes);
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
function normalizePath(pathValue) {
|
||||
if (!pathValue.startsWith('/')) {
|
||||
return '/' + pathValue;
|
||||
}
|
||||
return pathValue.replace(/\/{2,}/g, '/');
|
||||
}
|
||||
|
||||
function sortObject(obj, valueTransform = (v) => v) {
|
||||
const sorted = {};
|
||||
for (const key of Object.keys(obj).sort()) {
|
||||
const value = obj[key];
|
||||
sorted[key] = valueTransform(value);
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
function sortPathItem(pathItem) {
|
||||
const methods = {};
|
||||
for (const key of Object.keys(pathItem).sort()) {
|
||||
methods[key] = pathItem[key];
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
function writeAggregate(doc) {
|
||||
const str = yaml.stringify(doc, { sortMapEntries: true });
|
||||
fs.writeFileSync(OUTPUT, str, 'utf8');
|
||||
console.log(`[stella-compose] wrote aggregate spec to ${OUTPUT}`);
|
||||
}
|
||||
|
||||
function rewriteRefs(node, serviceName) {
|
||||
if (node === null || node === undefined) return node;
|
||||
|
||||
if (Array.isArray(node)) {
|
||||
return node.map((item) => rewriteRefs(item, serviceName));
|
||||
}
|
||||
|
||||
if (typeof node !== 'object') {
|
||||
return node;
|
||||
}
|
||||
|
||||
const clone = Array.isArray(node) ? [] : {};
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
if (key === '$ref' && typeof value === 'string') {
|
||||
clone[key] = normalizeRef(value, serviceName);
|
||||
} else {
|
||||
clone[key] = rewriteRefs(value, serviceName);
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
function normalizeRef(refValue, serviceName) {
|
||||
if (refValue.startsWith('../_shared/schemas/')) {
|
||||
const name = refValue.split('#/schemas/')[1];
|
||||
return `#/components/schemas/${name}`;
|
||||
}
|
||||
|
||||
const prefix = '#/components/schemas/';
|
||||
if (refValue.startsWith(prefix)) {
|
||||
const name = refValue.slice(prefix.length);
|
||||
if (name.includes('.')) {
|
||||
return refValue; // already namespaced
|
||||
}
|
||||
return `${prefix}${serviceName}.${name}`;
|
||||
}
|
||||
|
||||
return refValue;
|
||||
}
|
||||
|
||||
function mergeShared(aggregate, dir, key) {
|
||||
if (!dir || !fs.existsSync(dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'));
|
||||
for (const file of files.sort()) {
|
||||
const full = path.join(dir, file);
|
||||
const content = fs.readFileSync(full, 'utf8');
|
||||
const doc = yaml.parse(content, { prettyErrors: true });
|
||||
const entries = doc?.[key] || {};
|
||||
for (const [name, value] of Object.entries(entries)) {
|
||||
if (aggregate.components[key][name]) {
|
||||
throw new Error(`Shared ${key} collision for ${name}`);
|
||||
}
|
||||
aggregate.components[key][name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const services = readServiceSpecs();
|
||||
if (services.length === 0) {
|
||||
console.log('[stella-compose] no service specs found; writing empty aggregate');
|
||||
}
|
||||
const aggregate = mergeSpecs(services);
|
||||
writeAggregate(aggregate);
|
||||
}
|
||||
|
||||
main();
|
||||
237
src/Api/StellaOps.Api.OpenApi/export-center/openapi.yaml
Normal file
237
src/Api/StellaOps.Api.OpenApi/export-center/openapi.yaml
Normal file
@@ -0,0 +1,237 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: StellaOps Export Center API (stub)
|
||||
version: 0.0.1
|
||||
description: Health and metadata scaffold for Export Center; replace with real contracts
|
||||
as authored.
|
||||
servers:
|
||||
- url: https://export.stellaops.local
|
||||
description: Example Export Center endpoint
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
tags:
|
||||
- Health
|
||||
summary: Liveness probe
|
||||
responses:
|
||||
'200':
|
||||
description: Service is up
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
ok:
|
||||
value:
|
||||
status: ok
|
||||
service: export-center
|
||||
timestamp: '2025-11-18T00:00:00Z'
|
||||
'503':
|
||||
description: Service unhealthy or dependencies unavailable.
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
unhealthy:
|
||||
value:
|
||||
status: degraded
|
||||
service: export-center
|
||||
reason: object store unreachable
|
||||
timestamp: '2025-11-18T00:00:00Z'
|
||||
/healthz:
|
||||
get:
|
||||
summary: Service health
|
||||
tags:
|
||||
- Meta
|
||||
responses:
|
||||
'200':
|
||||
description: Service healthy
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthResponse'
|
||||
examples:
|
||||
ok:
|
||||
summary: Healthy response
|
||||
value:
|
||||
status: ok
|
||||
service: export-center
|
||||
'503':
|
||||
description: Service unavailable
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
examples:
|
||||
unavailable:
|
||||
summary: Unhealthy response
|
||||
value:
|
||||
code: service_unavailable
|
||||
message: mirror bundle backlog exceeds SLA
|
||||
traceId: 3
|
||||
/bundles/{bundleId}:
|
||||
get:
|
||||
tags:
|
||||
- Bundles
|
||||
summary: Download export bundle by id
|
||||
parameters:
|
||||
- name: bundleId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: bundle-2025-11-18-001
|
||||
responses:
|
||||
'200':
|
||||
description: Bundle stream
|
||||
content:
|
||||
application/zip:
|
||||
examples:
|
||||
download:
|
||||
summary: Zip payload
|
||||
value: binary data
|
||||
'404':
|
||||
description: Bundle not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
examples:
|
||||
notFound:
|
||||
summary: Bundle missing
|
||||
value:
|
||||
code: export.bundle_not_found
|
||||
message: Bundle bundle-2025-11-18-001 not found.
|
||||
traceId: 01JF04NF
|
||||
/bundles:
|
||||
get:
|
||||
tags:
|
||||
- Bundles
|
||||
summary: List export bundles
|
||||
parameters:
|
||||
- $ref: '../_shared/parameters/tenant.yaml#/parameters/TenantParam'
|
||||
- $ref: '../_shared/parameters/paging.yaml#/parameters/LimitParam'
|
||||
- $ref: '../_shared/parameters/paging.yaml#/parameters/CursorParam'
|
||||
responses:
|
||||
'200':
|
||||
description: Bundle page
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/BundleSummary'
|
||||
metadata:
|
||||
$ref: '../_shared/schemas/common.yaml#/schemas/PageMetadata'
|
||||
examples:
|
||||
page:
|
||||
summary: First page of bundles
|
||||
value:
|
||||
items:
|
||||
- bundleId: bundle-2025-11-18-001
|
||||
createdAt: '2025-11-18T12:00:00Z'
|
||||
status: ready
|
||||
sizeBytes: 1048576
|
||||
- bundleId: bundle-2025-11-18-000
|
||||
createdAt: '2025-11-18T10:00:00Z'
|
||||
status: ready
|
||||
sizeBytes: 2048
|
||||
metadata:
|
||||
hasMore: true
|
||||
nextCursor: eyJyIjoiMjAyNS0xMS0xOC0wMDIifQ
|
||||
'400':
|
||||
description: Invalid request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../_shared/schemas/common.yaml#/schemas/ErrorEnvelope'
|
||||
examples:
|
||||
invalidTenant:
|
||||
summary: Tenant missing
|
||||
value:
|
||||
code: export.invalid_tenant
|
||||
message: tenant query parameter is required.
|
||||
traceId: 01JF04ERR3
|
||||
/bundles/{bundleId}/manifest:
|
||||
get:
|
||||
tags:
|
||||
- Bundles
|
||||
summary: Fetch bundle manifest metadata
|
||||
parameters:
|
||||
- name: bundleId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Manifest metadata
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BundleManifest'
|
||||
examples:
|
||||
manifest:
|
||||
value:
|
||||
bundleId: bundle-2025-11-18-001
|
||||
contents:
|
||||
- type: advisory
|
||||
digest: sha256:abc123
|
||||
- type: vex
|
||||
digest: sha256:def456
|
||||
createdAt: '2025-11-18T12:00:00Z'
|
||||
'404':
|
||||
description: Bundle not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../_shared/schemas/common.yaml#/schemas/ErrorEnvelope'
|
||||
components:
|
||||
schemas:
|
||||
BundleSummary:
|
||||
type: object
|
||||
required:
|
||||
- bundleId
|
||||
- createdAt
|
||||
- status
|
||||
properties:
|
||||
bundleId:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- ready
|
||||
- building
|
||||
- failed
|
||||
sizeBytes:
|
||||
type: integer
|
||||
BundleManifest:
|
||||
type: object
|
||||
required:
|
||||
- bundleId
|
||||
- contents
|
||||
properties:
|
||||
bundleId:
|
||||
type: string
|
||||
contents:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [type, digest]
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
example: advisory
|
||||
digest:
|
||||
type: string
|
||||
example: sha256:abc123
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
HealthResponse:
|
||||
$ref: ../_shared/schemas/common.yaml#/schemas/HealthEnvelope
|
||||
Error:
|
||||
$ref: ../_shared/schemas/common.yaml#/schemas/ErrorEnvelope
|
||||
153
src/Api/StellaOps.Api.OpenApi/graph/openapi.yaml
Normal file
153
src/Api/StellaOps.Api.OpenApi/graph/openapi.yaml
Normal file
@@ -0,0 +1,153 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: StellaOps Graph API (stub)
|
||||
version: 0.0.1
|
||||
description: Health and dataset status scaffold for Graph service; replace with
|
||||
full contract as authored.
|
||||
servers:
|
||||
- url: https://graph.stellaops.local
|
||||
description: Example Graph endpoint
|
||||
paths:
|
||||
/healthz:
|
||||
get:
|
||||
summary: Service health
|
||||
tags:
|
||||
- Meta
|
||||
responses:
|
||||
'200':
|
||||
description: Service healthy
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthEnvelope'
|
||||
examples:
|
||||
ok:
|
||||
summary: Healthy response
|
||||
value:
|
||||
status: ok
|
||||
service: graph
|
||||
'503':
|
||||
description: Service unavailable
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
examples:
|
||||
unavailable:
|
||||
summary: Unhealthy response
|
||||
value:
|
||||
code: service_unavailable
|
||||
message: indexer lag exceeds threshold
|
||||
traceId: 5
|
||||
/graphs/{graphId}/status:
|
||||
get:
|
||||
summary: Get graph build status
|
||||
tags:
|
||||
- Graphs
|
||||
parameters:
|
||||
- name: graphId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- $ref: ../_shared/parameters/tenant.yaml#/parameters/TenantParam
|
||||
responses:
|
||||
'200':
|
||||
description: Graph status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GraphStatus'
|
||||
examples:
|
||||
ready:
|
||||
value:
|
||||
graphId: graph-01JF0XYZ
|
||||
status: ready
|
||||
builtAt: 2025-11-18 12:00:00+00:00
|
||||
'404':
|
||||
description: Graph not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
/graphs/{graphId}/nodes:
|
||||
get:
|
||||
summary: List graph nodes
|
||||
tags:
|
||||
- Graphs
|
||||
parameters:
|
||||
- name: graphId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- $ref: ../_shared/parameters/paging.yaml#/parameters/LimitParam
|
||||
- $ref: ../_shared/parameters/paging.yaml#/parameters/CursorParam
|
||||
responses:
|
||||
'200':
|
||||
description: Graph nodes page
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GraphNodePage'
|
||||
examples:
|
||||
sample:
|
||||
value:
|
||||
nodes:
|
||||
- id: node-1
|
||||
kind: artifact
|
||||
label: registry.stella-ops.local/runtime/api
|
||||
- id: node-2
|
||||
kind: policy
|
||||
label: policy:baseline
|
||||
metadata:
|
||||
hasMore: true
|
||||
nextCursor: eyJuIjoiMjAyNS0xMS0xOCJ9
|
||||
'404':
|
||||
description: Graph not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../_shared/schemas/common.yaml#/schemas/ErrorEnvelope
|
||||
components:
|
||||
schemas:
|
||||
GraphStatus:
|
||||
type: object
|
||||
required:
|
||||
- graphId
|
||||
- status
|
||||
properties:
|
||||
graphId:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- building
|
||||
- ready
|
||||
- failed
|
||||
builtAt:
|
||||
type: string
|
||||
format: date-time
|
||||
GraphNodePage:
|
||||
type: object
|
||||
required:
|
||||
- nodes
|
||||
- metadata
|
||||
properties:
|
||||
nodes:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- kind
|
||||
- label
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
label:
|
||||
type: string
|
||||
metadata:
|
||||
$ref: ../_shared/schemas/common.yaml#/schemas/PageMetadata
|
||||
220
src/Api/StellaOps.Api.OpenApi/orchestrator/openapi.yaml
Normal file
220
src/Api/StellaOps.Api.OpenApi/orchestrator/openapi.yaml
Normal file
@@ -0,0 +1,220 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: StellaOps Orchestrator API (stub)
|
||||
version: 0.0.1
|
||||
description: Health and job orchestration scaffold for Orchestrator service; replace
|
||||
with real contracts as contracts are authored.
|
||||
servers:
|
||||
- url: https://orchestrator.stellaops.local
|
||||
description: Example Orchestrator endpoint
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
tags:
|
||||
- Health
|
||||
summary: Liveness probe
|
||||
responses:
|
||||
'200':
|
||||
description: Service is up
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
ok:
|
||||
value:
|
||||
status: ok
|
||||
service: orchestrator
|
||||
timestamp: '2025-11-18T00:00:00Z'
|
||||
'503':
|
||||
description: Service unhealthy or dependencies unavailable.
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
unhealthy:
|
||||
value:
|
||||
status: degraded
|
||||
service: orchestrator
|
||||
reason: scheduler queue unreachable
|
||||
timestamp: '2025-11-18T00:00:00Z'
|
||||
/healthz:
|
||||
get:
|
||||
summary: Service health
|
||||
tags:
|
||||
- Meta
|
||||
responses:
|
||||
'200':
|
||||
description: Service healthy
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../_shared/schemas/common.yaml#/schemas/HealthEnvelope
|
||||
examples:
|
||||
ok:
|
||||
summary: Healthy response
|
||||
value:
|
||||
status: ok
|
||||
service: orchestrator
|
||||
'503':
|
||||
description: Service unavailable
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../_shared/schemas/common.yaml#/schemas/ErrorEnvelope
|
||||
examples:
|
||||
unavailable:
|
||||
summary: Unhealthy response
|
||||
value:
|
||||
code: service_unavailable
|
||||
message: outbound queue lag exceeds threshold
|
||||
traceId: 1
|
||||
/jobs:
|
||||
post:
|
||||
tags:
|
||||
- Jobs
|
||||
summary: Submit a job to the orchestrator queue
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobCreateRequest'
|
||||
examples:
|
||||
scanJob:
|
||||
summary: Submit scan job
|
||||
value:
|
||||
kind: scan
|
||||
payload:
|
||||
artifactId: registry.stella-ops.local/runtime/api
|
||||
policyVersion: 2025.10.1
|
||||
priority: high
|
||||
tenant: tenant-alpha
|
||||
security:
|
||||
- OAuthClientCredentials: []
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
'202':
|
||||
description: Job accepted
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobCreateResponse'
|
||||
examples:
|
||||
accepted:
|
||||
summary: Job enqueued
|
||||
value:
|
||||
jobId: job_01JF04ABCD
|
||||
status: queued
|
||||
queue: scan
|
||||
enqueuedAt: '2025-11-18T12:00:00Z'
|
||||
'400':
|
||||
description: Invalid request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../_shared/schemas/common.yaml#/schemas/ErrorEnvelope
|
||||
examples:
|
||||
missingType:
|
||||
summary: Missing jobType
|
||||
value:
|
||||
code: orch.invalid_request
|
||||
message: jobType is required.
|
||||
traceId: 01JF04ERR1
|
||||
get:
|
||||
tags:
|
||||
- Jobs
|
||||
summary: List jobs
|
||||
parameters:
|
||||
- in: query
|
||||
name: status
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- queued
|
||||
- running
|
||||
- failed
|
||||
- completed
|
||||
- $ref: ../_shared/parameters/paging.yaml#/parameters/LimitParam
|
||||
- $ref: ../_shared/parameters/tenant.yaml#/parameters/TenantParam
|
||||
responses:
|
||||
'200':
|
||||
description: Jobs page
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/JobSummary'
|
||||
examples:
|
||||
sample:
|
||||
value:
|
||||
- jobId: job_01JF04ABCD
|
||||
status: queued
|
||||
queue: scan
|
||||
enqueuedAt: '2025-11-18T12:00:00Z'
|
||||
- jobId: job_01JF04EFGH
|
||||
status: running
|
||||
queue: policy-eval
|
||||
enqueuedAt: '2025-11-18T11:55:00Z'
|
||||
startedAt: '2025-11-18T11:56:10Z'
|
||||
/jobs/{jobId}:
|
||||
get:
|
||||
tags:
|
||||
- Jobs
|
||||
summary: Get job status
|
||||
parameters:
|
||||
- name: jobId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Job status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JobSummary'
|
||||
examples:
|
||||
sample:
|
||||
value:
|
||||
jobId: job_01JF04ABCD
|
||||
status: queued
|
||||
queue: scan
|
||||
enqueuedAt: '2025-11-18T12:00:00Z'
|
||||
'404':
|
||||
description: Job not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
components:
|
||||
schemas:
|
||||
JobSummary:
|
||||
type: object
|
||||
required:
|
||||
- jobId
|
||||
- status
|
||||
- queue
|
||||
- enqueuedAt
|
||||
properties:
|
||||
jobId:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- queued
|
||||
- running
|
||||
- failed
|
||||
- completed
|
||||
queue:
|
||||
type: string
|
||||
enqueuedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
startedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
completedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
tenant:
|
||||
type: string
|
||||
162
src/Api/StellaOps.Api.OpenApi/policy/openapi.yaml
Normal file
162
src/Api/StellaOps.Api.OpenApi/policy/openapi.yaml
Normal file
@@ -0,0 +1,162 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: StellaOps Policy Engine API (stub)
|
||||
version: 0.0.1
|
||||
description: Health + evaluation scaffold for Policy Engine; replace with real contracts
|
||||
as authored.
|
||||
servers:
|
||||
- url: https://policy.stellaops.local
|
||||
description: Example Policy Engine endpoint
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
tags:
|
||||
- Health
|
||||
summary: Liveness probe
|
||||
responses:
|
||||
'200':
|
||||
description: Service is up
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
ok:
|
||||
value:
|
||||
status: ok
|
||||
service: policy
|
||||
timestamp: '2025-11-18T00:00:00Z'
|
||||
'503':
|
||||
description: Service unhealthy or dependencies unavailable.
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
unhealthy:
|
||||
value:
|
||||
status: degraded
|
||||
service: policy
|
||||
reason: mongo unavailable
|
||||
timestamp: '2025-11-18T00:00:00Z'
|
||||
/healthz:
|
||||
get:
|
||||
summary: Service health
|
||||
tags:
|
||||
- Meta
|
||||
responses:
|
||||
'200':
|
||||
description: Service healthy
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../_shared/schemas/common.yaml#/schemas/HealthEnvelope
|
||||
examples:
|
||||
ok:
|
||||
summary: Healthy response
|
||||
value:
|
||||
status: ok
|
||||
service: policy
|
||||
'503':
|
||||
description: Service unavailable
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../_shared/schemas/common.yaml#/schemas/ErrorEnvelope
|
||||
examples:
|
||||
unavailable:
|
||||
summary: Unhealthy response
|
||||
value:
|
||||
code: service_unavailable
|
||||
message: projector backlog exceeds SLA
|
||||
traceId: 2
|
||||
/evaluate:
|
||||
post:
|
||||
tags:
|
||||
- Evaluation
|
||||
summary: Evaluate policy for an artifact
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/EvaluationRequest'
|
||||
examples:
|
||||
default:
|
||||
summary: Evaluate current policy for an artifact
|
||||
value:
|
||||
artifactId: registry.stella-ops.local/runtime/api
|
||||
policyVersion: 2025.10.1
|
||||
inputs:
|
||||
tenant: acme
|
||||
branch: main
|
||||
responses:
|
||||
'200':
|
||||
description: Evaluation succeeded
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
allow:
|
||||
summary: Allow decision with reasons
|
||||
value:
|
||||
decision: allow
|
||||
policyVersion: 2025.10.1
|
||||
traceId: 01JF040XYZ
|
||||
reasons:
|
||||
- signed
|
||||
- within SLO
|
||||
metadata:
|
||||
latencyMs: 42
|
||||
obligations:
|
||||
- record: evidence
|
||||
schema:
|
||||
$ref: '#/components/schemas/EvaluationResponse'
|
||||
'400':
|
||||
description: Invalid request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../_shared/schemas/common.yaml#/schemas/ErrorEnvelope
|
||||
examples:
|
||||
missingArtifact:
|
||||
summary: Missing artifactId
|
||||
value:
|
||||
code: policy.invalid_request
|
||||
message: artifactId is required.
|
||||
traceId: 01JF041ERR
|
||||
security:
|
||||
- OAuthClientCredentials: []
|
||||
- BearerAuth: []
|
||||
components:
|
||||
schemas:
|
||||
EvaluationRequest:
|
||||
type: object
|
||||
required:
|
||||
- artifactId
|
||||
properties:
|
||||
artifactId:
|
||||
type: string
|
||||
example: registry.stella-ops.local/runtime/api
|
||||
policyVersion:
|
||||
type: string
|
||||
example: 2025.10.1
|
||||
inputs:
|
||||
type: object
|
||||
EvaluationResponse:
|
||||
type: object
|
||||
required:
|
||||
- decision
|
||||
properties:
|
||||
decision:
|
||||
type: string
|
||||
enum:
|
||||
- allow
|
||||
- deny
|
||||
policyVersion:
|
||||
type: string
|
||||
traceId:
|
||||
type: string
|
||||
reasons:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
obligations:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
129
src/Api/StellaOps.Api.OpenApi/scheduler/openapi.yaml
Normal file
129
src/Api/StellaOps.Api.OpenApi/scheduler/openapi.yaml
Normal file
@@ -0,0 +1,129 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: StellaOps Scheduler API (stub)
|
||||
version: 0.0.1
|
||||
description: Health and queue status scaffold for Scheduler service; replace with full contract as authored.
|
||||
servers:
|
||||
- url: https://scheduler.stellaops.local
|
||||
description: Example Scheduler endpoint
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
tags:
|
||||
- Health
|
||||
summary: Liveness probe
|
||||
responses:
|
||||
'200':
|
||||
description: Service is up
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
ok:
|
||||
value:
|
||||
status: ok
|
||||
service: scheduler
|
||||
timestamp: '2025-11-18T00:00:00Z'
|
||||
'503':
|
||||
description: Service unhealthy or dependencies unavailable.
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
unhealthy:
|
||||
value:
|
||||
status: degraded
|
||||
service: scheduler
|
||||
reason: queue not reachable
|
||||
timestamp: '2025-11-18T00:00:00Z'
|
||||
/healthz:
|
||||
get:
|
||||
summary: Service health
|
||||
tags:
|
||||
- Meta
|
||||
responses:
|
||||
'200':
|
||||
description: Service healthy
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthEnvelope'
|
||||
examples:
|
||||
ok:
|
||||
summary: Healthy response
|
||||
value:
|
||||
status: ok
|
||||
service: scheduler
|
||||
'503':
|
||||
description: Service unavailable
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
examples:
|
||||
unavailable:
|
||||
summary: Unhealthy response
|
||||
value:
|
||||
code: service_unavailable
|
||||
message: queue backlog exceeds threshold
|
||||
traceId: 4
|
||||
/queues/{name}:
|
||||
get:
|
||||
tags:
|
||||
- Queues
|
||||
summary: Get queue status
|
||||
parameters:
|
||||
- name: name
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: default
|
||||
responses:
|
||||
'200':
|
||||
description: Queue status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/QueueStatus'
|
||||
examples:
|
||||
status:
|
||||
summary: Queue depth snapshot
|
||||
value:
|
||||
name: default
|
||||
depth: 12
|
||||
inflight: 2
|
||||
oldestAgeSeconds: 45
|
||||
updatedAt: '2025-11-18T12:00:00Z'
|
||||
'404':
|
||||
description: Queue not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
examples:
|
||||
notFound:
|
||||
summary: Queue missing
|
||||
value:
|
||||
code: scheduler.queue_not_found
|
||||
message: Queue default not found.
|
||||
traceId: 01JF04NF2
|
||||
components:
|
||||
schemas:
|
||||
QueueStatus:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- depth
|
||||
- inflight
|
||||
- updatedAt
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
depth:
|
||||
type: integer
|
||||
inflight:
|
||||
type: integer
|
||||
oldestAgeSeconds:
|
||||
type: integer
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
1542
src/Api/StellaOps.Api.OpenApi/stella.yaml
Normal file
1542
src/Api/StellaOps.Api.OpenApi/stella.yaml
Normal file
File diff suppressed because it is too large
Load Diff
10
src/Api/StellaOps.Api.OpenApi/tasks.md
Normal file
10
src/Api/StellaOps.Api.OpenApi/tasks.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# API Contracts Tasks
|
||||
|
||||
| Task | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| OAS-61-001 | DONE | Scaffold per-service OpenAPI 3.1 files with shared components, info blocks, and initial path stubs. |
|
||||
| OAS-61-002 | DONE (2025-11-18) | Composer (`compose.mjs`) emits `stella.yaml` with namespaced paths/components; CI job validates aggregate stays up to date. |
|
||||
| OAS-62-001 | DOING | Populate request/response examples for top 50 endpoints, including standard error envelope. |
|
||||
| OAS-62-002 | TODO | Add custom lint rules enforcing pagination, idempotency headers, naming conventions, and example coverage. |
|
||||
| OAS-63-001 | TODO | Implement compatibility diff tooling comparing previous release specs; classify breaking vs additive changes. |
|
||||
| OAS-63-002 | TODO | Add `/.well-known/openapi` discovery endpoint schema metadata (extensions, version info). |
|
||||
Reference in New Issue
Block a user