fix: replay accepted delegation without reserving active Workdirs

This commit is contained in:
2026-10-20 07:24:51 +09:00
parent e39372a52a
commit 9005856f77
10 changed files with 669 additions and 15 deletions
+3 -5
View File
@@ -1351,6 +1351,7 @@ pub trait ServerApi {
status = 200,
error_status = 400,
additional_error_statuses = [401, 403, 404, 500],
bearer_auth = true,
browser_auth = true,
)]
async fn subjektiv_subject_permissions_get(
@@ -1365,6 +1366,7 @@ pub trait ServerApi {
status = 200,
error_status = 400,
additional_error_statuses = [401, 403, 404, 409, 500],
bearer_auth = true,
browser_auth = true,
)]
async fn subjektiv_subject_permissions_set(
@@ -14697,11 +14699,7 @@ mod openapi_artifact_tests {
// the Runtime/Worker-source signed operation inventory above.
const OTHER_OPENAPI_EXCLUDED: &[&str] = &["worker_session", "worker_session_history"];
const DOCUMENTED_SIGNED_INTERNAL: &[&str] = &["workspace_runtime_config"];
const BROWSER_ONLY: &[&str] = &[
"merge_request_selector_repair",
"subjektiv_subject_permissions_get",
"subjektiv_subject_permissions_set",
];
const BROWSER_ONLY: &[&str] = &["merge_request_selector_repair"];
let document = canonical_openapi_document().expect("canonical OpenAPI contract must build");
let value: serde_json::Value =
+32
View File
@@ -5708,6 +5708,38 @@ impl WorkspaceApi {
if let Some(grant) = request.resolved_execution_grant.as_ref() {
subject_authorization::bind_reserved_grant(self, grant, &worker_id.to_string())?;
}
// A completed exact-source allocation is historical acceptance, not a new
// launch. Source/grant authorization and the full fingerprint were checked
// above. Do not reserve its already-linked attachments again, redispatch
// initial input, or compensate an existing Worker on a replay failure.
let reserved_worker = RuntimeWorkerRef::new(runtime_id, worker_id.to_string());
if request.resolved_control_operation.is_some()
&& !self
.store
.has_active_worker_create_reservation(&self.config.workspace_id, &reserved_worker)?
{
let record = self
.store
.get_worker_registry(&self.config.workspace_id, &reserved_worker)?
.ok_or_else(|| {
Error::Store("completed Worker allocation has no registry evidence".into())
})?;
let summary = self
.runtime
.worker(&reserved_worker)
.unwrap_or_else(|_| worker_summary_from_registry(&record));
return Ok(WorkerSpawnResult {
state: InternalWorkerOperationState::Accepted,
worker: Some(summary),
acceptance_evidence: vec![crate::hosts::WorkerSpawnAcceptanceEvidence {
kind: "saved_creation".into(),
detail:
"Exact allocation was previously accepted; no new execution was requested"
.into(),
}],
diagnostics: Vec::new(),
});
}
if let Some(assignment) = request.ticket_assignment.as_ref() {
self.store.bind_ticket_assignment_operation_worker(
&self.config.workspace_id,
@@ -1,8 +1,9 @@
//! Normal Server library boundary tests (`subject_authorization_tests` filter).
//! Router authentication, Host creation/reservation, Subject policy storage, Worker
//! Feature installation and Ticket SQLite writes are real. Only Runtime execution
//! is replaced by WorkdirlessFixtureRuntime; the unused model never runs. These
//! are not product E2E tests and do not claim OS isolation or child tool execution.
//! Feature installation and Ticket SQLite writes are real. The original boundary
//! cases substitute WorkdirlessFixtureRuntime. The connected regression below also
//! runs a real ProfileRuntimeWorkerFactory/Controller and materialized Workdir;
//! only its external model stream is scripted. Neither fixture claims OS isolation.
use super::*;
use agen::tool::{Tool, ToolExecutionContext, ToolOutput};
use server_api::authorization::{
@@ -1528,3 +1529,620 @@ async fn signed_host_scope_guards_real_local_read_write_and_revocation_before_ef
);
session.close().await.unwrap();
}
/// Script only the external model boundary. Tool results come back through the
/// production Controller; this model never reads or writes the fixture itself.
#[derive(Clone)]
struct ConnectedAuthorizationModel {
completed: tokio::sync::mpsc::Sender<Vec<(String, bool, String)>>,
}
#[async_trait::async_trait]
impl agen::llm_client::LlmClient for ConnectedAuthorizationModel {
async fn stream(
&self,
request: agen::llm_client::Request,
) -> std::result::Result<
std::pin::Pin<
Box<
dyn futures::Stream<
Item = std::result::Result<
agen::llm_client::event::Event,
agen::llm_client::error::ClientError,
>,
> + Send,
>,
>,
agen::llm_client::error::ClientError,
> {
use agen::llm_client::{
event::{Event, StopReason},
types::Item,
};
let results: Vec<_> = request
.items
.iter()
.filter_map(|item| match item {
Item::ToolResult {
call_id,
is_error,
content,
summary,
..
} if matches!(call_id.as_str(), "connected-read" | "connected-write") => Some((
call_id.clone(),
*is_error,
format!("{summary}; {content:?}"),
)),
_ => None,
})
.collect();
let events = if results.iter().any(|(id, _, _)| id == "connected-write") {
// A bounded channel is our completion barrier: no timers, sleeps or
// filesystem polling decide whether the real tools have executed.
let _ = self.completed.try_send(results);
vec![
Event::text_block_start(0),
Event::text_delta(0, "connected tools finished"),
Event::text_block_stop(0, Some(StopReason::EndTurn)),
]
} else {
let (id, name, input) = if results.iter().any(|(id, _, _)| id == "connected-read") {
(
"connected-write",
"Write",
json!({"target_workdir":"workdir", "file_path":"README.md", "content":"MUST_NOT_REPLACE_README\n"}),
)
} else {
(
"connected-read",
"Read",
json!({"target_workdir":"workdir", "file_path":"README.md"}),
)
};
assert!(
request.tools.iter().any(|tool| tool.name == name),
"editing Profile must expose {name}; denial must not be schema absence"
);
vec![
Event::tool_use_start(0, id, name),
Event::tool_input_delta(0, input.to_string()),
Event::tool_use_stop(0),
]
};
Ok(Box::pin(futures::stream::iter(events.into_iter().map(Ok))))
}
fn clone_boxed(&self) -> Box<dyn agen::llm_client::LlmClient> {
Box::new(self.clone())
}
}
/// Use the same signed source protocol as RuntimeOwnedWorkspaceClient, with a
/// per-test audience and listener rather than the older server-test/8787 fixture.
async fn connected_authorization_post(
base_url: &str,
identity: &RuntimeIdentityMaterial,
source: &RuntimeWorkerRef,
path: &str,
body: &Value,
) -> (StatusCode, Value) {
let bytes = serde_json::to_vec(body).unwrap();
let proof = worker_runtime::auth::RuntimeRequestSourceSigner::from_identity(identity)
.issue(
base_url,
TEST_WORKSPACE_ID,
Some(&source.worker_id),
worker_runtime::auth::WORKSPACE_REQUEST_PERMISSION,
"POST",
path,
&bytes,
i64::try_from(worker_runtime::auth::unix_now_seconds()).unwrap(),
30,
)
.unwrap();
let response = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build()
.unwrap()
.post(format!("{base_url}{path}"))
.header(CONTENT_TYPE, "application/json")
.header(
worker_runtime::auth::RUNTIME_REQUEST_SOURCE_PROOF_HEADER,
proof,
)
.body(bytes)
.send()
.await
.unwrap();
let status = StatusCode::from_u16(response.status().as_u16()).unwrap();
let bytes = response.bytes().await.unwrap();
let body = serde_json::from_slice(&bytes)
.unwrap_or_else(|_| json!({"text":String::from_utf8_lossy(&bytes)}));
(status, body)
}
// The Runtime HTTP boundary owns synchronous lifecycle handlers. Give it an
// independent scheduler, matching separate product processes: a blocked Runtime
// request must not pin the Backend callback's Tokio local scheduling queue.
struct ConnectedRuntimeServer {
shutdown: Option<tokio::sync::oneshot::Sender<()>>,
thread: Option<std::thread::JoinHandle<()>>,
}
impl ConnectedRuntimeServer {
fn start(listener: tokio::net::TcpListener, router: Router) -> Self {
let listener = listener.into_std().unwrap();
let (shutdown, stopped) = tokio::sync::oneshot::channel();
let thread = std::thread::spawn(move || {
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
let listener = tokio::net::TcpListener::from_std(listener).unwrap();
axum::serve(listener, router)
.with_graceful_shutdown(async move {
let _ = stopped.await;
})
.await
.unwrap();
});
runtime.shutdown_timeout(std::time::Duration::from_secs(5));
});
Self {
shutdown: Some(shutdown),
thread: Some(thread),
}
}
}
impl Drop for ConnectedRuntimeServer {
fn drop(&mut self) {
if let Some(shutdown) = self.shutdown.take() {
let _ = shutdown.send(());
}
if let Some(thread) = self.thread.take() {
thread.join().unwrap();
}
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 6)]
async fn connected_read_only_subject_delegates_editing_profile_to_real_runtime_read_but_not_write()
{
use worker_runtime::working_directory::WorkingDirectoryMaterializer;
const RUNTIME: &str = "connected-subject-authorization";
let dir = tempfile::tempdir().unwrap();
init_clean_git_workspace(dir.path());
let backend_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let runtime_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let backend_url = format!("http://{}", backend_listener.local_addr().unwrap());
let runtime_url = format!("http://{}", runtime_listener.local_addr().unwrap());
let identity = RuntimeIdentityMaterial::generate(RUNTIME).unwrap();
let runtime_root = dir.path().join("connected-runtime");
let materializer = worker_runtime::working_directory::RuntimeGitMaterializer::new(
dir.path().join("connected-workdirs"),
);
let (completed, mut completion) = tokio::sync::mpsc::channel(4);
let model = ConnectedAuthorizationModel { completed };
let factory = worker_runtime::worker_backend::ProfileRuntimeWorkerFactory::new(
dir.path().join("connected-profiles"),
)
.with_remote_worker_mutation_identity(identity.clone())
.with_workspace_request_client(
worker_runtime::workspace_request::RuntimeWorkspaceRequestClient::new(
TEST_WORKSPACE_ID,
&backend_url,
RUNTIME,
)
.with_runtime_request_source(&identity, &backend_url),
)
.with_runtime_store_dir(&runtime_root)
.with_spawn_model_client_factory(move || Box::new(model.clone()));
let execution = worker_runtime::worker_backend::WorkerRuntimeExecutionBackend::new(factory)
.unwrap()
.with_working_directory_materializer(materializer.clone());
let runtime = worker_runtime::Runtime::with_fs_store_and_execution_backend(
worker_runtime::fs_store::FsRuntimeStoreOptions {
root: runtime_root,
runtime_id: RUNTIME.into(),
display_name: None,
},
Arc::new(execution),
)
.unwrap();
let mut config = test_server_config(dir.path());
config.backend_base_url = Some(backend_url.clone());
// The embedded Runtime is unused. Both source and child below execute on the
// connected real Runtime above, not on this existing Host fixture backend.
let control = Arc::new(test_control_store(&config));
seed_test_registered_workspace(control.as_ref(), &config)
.await
.unwrap();
let api = WorkspaceApi::new_with_execution_backend(
config,
control,
Arc::new(DeterministicExecutionBackend::default()),
)
.await
.unwrap();
let now = now_registry_timestamp();
api.store
.upsert_workspace_runtime_binding_record(
WorkspaceRuntimeBinding {
workspace_id: TEST_WORKSPACE_ID.into(),
runtime_id: RUNTIME.into(),
display_name: RUNTIME.into(),
base_url: runtime_url.clone(),
public_key: identity.public_key.clone(),
public_key_fingerprint: String::new(),
binding_id: "connected-authorization-binding".into(),
authentication_mode: StoredRuntimeAuthenticationMode::WorkspaceIdentity,
created_at: now.clone(),
updated_at: now,
revoked_at: None,
},
false,
)
.await
.unwrap();
let binding = api
.store
.get_workspace_runtime_binding(TEST_WORKSPACE_ID, RUNTIME)
.await
.unwrap()
.unwrap();
// Repository materialization and Worker startup must authenticate as this
// Workspace, not the Runtime HTTP local-token fallback's `local` Workspace.
let workspace_identity = api
.signing_identities
.provision_existing(TEST_WORKSPACE_ID, &test_owner_actor().account_id)
.unwrap();
let verifier = worker_runtime::workspace_issuer::WorkspaceCapabilityVerifier::new(
vec![
worker_runtime::workspace_issuer::WorkspaceIssuerTrustRecord {
workspace_id: TEST_WORKSPACE_ID.into(),
backend_url: backend_url.clone(),
key_id: workspace_identity.key_id.clone(),
algorithm: workspace_identity.algorithm.clone(),
public_key: workspace_identity.public_key.clone().unwrap(),
public_key_fingerprint: workspace_identity.public_key_fingerprint.clone().unwrap(),
trust_id: "connected-authorization-workspace-trust".into(),
state: worker_runtime::workspace_issuer::WorkspaceIssuerTrustState::Active,
registered_at_unix: 1,
updated_at_unix: 1,
},
],
Arc::new(
worker_runtime::workspace_issuer::InMemoryWorkspaceClaimReplayProtection::default(),
),
)
.unwrap();
let runtime_errors = Arc::new(Mutex::new(Vec::<String>::new()));
let captured_errors = runtime_errors.clone();
let _runtime_server = ConnectedRuntimeServer::start(
runtime_listener,
worker_runtime::http_server::runtime_http_router_with_workspace_auth(
runtime.clone(),
None,
worker_runtime::http_server::WorkspaceRuntimeHttpAuth {
verifier,
runtime_id: RUNTIME.into(),
},
)
.layer(axum::middleware::from_fn(
move |request: axum::extract::Request, next: axum::middleware::Next| {
let errors = captured_errors.clone();
async move {
let response = next.run(request).await;
if response.status().is_success() {
return response;
}
let (parts, body) = response.into_parts();
let bytes = to_bytes(body, 2 * 1024 * 1024).await.unwrap();
let body: Value = serde_json::from_slice(&bytes).unwrap();
errors.lock().unwrap().push(
body["error"]["message"]
.as_str()
.unwrap_or("unknown fixture error")
.to_owned(),
);
Response::from_parts(parts, Body::from(bytes))
}
},
)),
);
api.runtime.register_or_replace(
RemoteWorkerRuntime::new(
RemoteRuntimeConfig {
runtime_id: RUNTIME.into(),
workspace_id: Some(TEST_WORKSPACE_ID.into()),
display_name: RUNTIME.into(),
base_url: runtime_url,
bearer_token: None,
workspace_authorization: Some(crate::hosts::WorkspaceRuntimeAuthorization::new(
api.store.clone(),
api.signing_identities.clone(),
backend_url.clone(),
binding,
)),
strict_public_egress: false,
cached_worker_creation_available: true,
cached_os: "linux".into(),
cached_arch: "x86_64".into(),
cached_status: "active".into(),
timeout: std::time::Duration::from_secs(30),
},
TEST_WORKSPACE_ID.to_string(),
backend_url.clone(),
)
.unwrap(),
);
install_subject_profiles(&api, "workspace_write");
seed_test_delivery_binding(&api);
let authorizations = Arc::new(Mutex::new(Vec::<(Value, StatusCode)>::new()));
let observed = authorizations.clone();
let app = build_router(api.clone()).layer(axum::middleware::from_fn(
move |request: axum::extract::Request, next: axum::middleware::Next| {
let observed = observed.clone();
async move {
if request.uri().path() != authorization_path() {
return next.run(request).await;
}
let (parts, body) = request.into_parts();
let bytes = to_bytes(body, 2 * 1024 * 1024).await.unwrap();
let permission: Value = serde_json::from_slice(&bytes).unwrap();
let response = next
.run(Request::from_parts(parts, Body::from(bytes)))
.await;
observed
.lock()
.unwrap()
.push((permission, response.status()));
response
}
},
));
let _backend_server = AuthorizationCallbackServer::start(backend_listener, app);
let owner =
seed_test_workspace_owner_token(api.store.as_ref(), "connected-authorization-owner").await;
let (status, created_workdir) = response(&api, human_request(&owner, "POST",
&format!("/api/w/{TEST_WORKSPACE_ID}/working-directories"),
Some(json!({"runtime_id":RUNTIME, "repository_key":"test-repository", "selector":"HEAD", "operation_id":"connected-authorization-workdir"})))).await;
assert_eq!(status, StatusCode::CREATED, "{created_workdir}");
let workdir_id = created_workdir["item"]["working_directory_id"]
.as_str()
.unwrap()
.to_owned();
let binding = materializer
.bind_working_directory(&workdir_id, None)
.unwrap();
let readme = binding.root().join("README.md");
assert_eq!(fs::read_to_string(&readme).unwrap(), "clean\n");
let subject_store = open_subjektiv_store(&api).unwrap();
let subject = subject_store
.create_subject(crate::subjektiv::SubjectRole::new("connected-authorization").unwrap())
.unwrap()
.id;
let read_permission = ScopedPermission {
operation: "filesystem.read".into(),
resource: PermissionResource::Workdir {
working_directory_id: workdir_id.clone(),
path: ".".into(),
recursive: true,
},
};
let mut child_permissions: Vec<_> = [
"task",
"web",
"image",
"sub_worker",
"flow",
"worker",
"workspace_worker_discovery",
"objective",
"manage_workdir",
"workdir_catalog",
"drive",
"ticket",
"merge_request",
"subjektiv",
]
.into_iter()
.map(activation)
.collect();
child_permissions.push(read_permission.clone());
// Set policy before startup: the actual source Controller sees this policy,
// not an unset body or a registry/singleton row manufactured by the test.
let (status, body) = response(&api, human_request(&owner, "POST",
&format!("/api/w/{TEST_WORKSPACE_ID}/subjektiv/subjects/{subject}/permissions"),
Some(json!({"operation_id":"connected-authorization-policy", "expected_change_id":null,
"execution":(["task", "subjektiv", "web", "image", "sub_worker", "worker", "workspace_worker_discovery", "objective", "manage_workdir", "workdir_catalog", "drive", "ticket"].into_iter().map(activation).collect::<Vec<_>>()),
"delegation":child_permissions.clone()})))).await;
assert_eq!(status, StatusCode::OK, "{body}");
let Json(source) = create_workspace_worker(
State(api.clone()),
HeaderMap::new(),
Json(CreateWorkspaceWorkerRequest {
runtime_id: RUNTIME.into(),
display_name: "Connected read-only Subject body".into(),
singleton_key: None,
profile: Some("project:authority-source".into()),
ticket_assignment: None,
initial_submit: vec![],
workdir_attachments: vec![],
feature_connections: server_api::WorkspaceWorkerFeatureConnectionsRequest {
subjektiv: Some(server_api::SubjektivWorkerConnectionRequest {
subject_id: subject.clone(),
}),
},
control_operation_id: None,
execution_grant: None,
}),
)
.await
.unwrap_or_else(|error| {
panic!(
"source creation: {error:?}; Runtime: {:?}",
runtime_errors.lock().unwrap()
)
});
let source = RuntimeWorkerRef::new(source.runtime_id, source.worker_id);
assert!(
api.store
.list_worker_workdir_links(TEST_WORKSPACE_ID, &source)
.unwrap()
.is_empty()
);
assert!(
api.store
.require_current_worker_singleton_owner(TEST_WORKSPACE_ID, &source)
.unwrap()
.is_some()
);
let (status, body) = connected_authorization_post(
&backend_url,
&identity,
&source,
&authorization_path(),
&serde_json::to_value(&read_permission).unwrap(),
)
.await;
assert_eq!(
status,
StatusCode::FORBIDDEN,
"body cannot execute its delegation ceiling: {body}"
);
// The normal noBash companion has no direct WorkerSpawn tool. Exercise its
// signed source CreateWorkspaceWorkerRequest transport explicitly, as opposed
// to installing a test-only privileged Feature or forging a child grant row.
let request = CreateWorkspaceWorkerRequest {
runtime_id: RUNTIME.into(),
display_name: "Connected ordinary editing Profile child".into(),
singleton_key: None,
profile: Some("project:authority-editor".into()),
ticket_assignment: None,
initial_submit: vec![protocol::Segment::Text {
content: "Read README.md, then attempt the denied Write.".into(),
}],
workdir_attachments: vec![server_api::BrowserWorkerWorkingDirectorySelection {
alias: "workdir".into(),
working_directory_id: workdir_id.clone(),
relative_cwd: None,
}],
feature_connections: Default::default(),
control_operation_id: Some("connected-authorization-child-once".into()),
execution_grant: Some(server_api::authorization::WorkerDelegationRequest {
permissions: child_permissions.clone(),
redelegation: vec![],
}),
};
let path = format!("/api/w/{TEST_WORKSPACE_ID}/worker-control/workers");
authorizations.lock().unwrap().clear();
let wire = serde_json::to_value(request).unwrap();
let (status, child) =
connected_authorization_post(&backend_url, &identity, &source, &path, &wire).await;
assert_eq!(
status,
StatusCode::OK,
"normal signed source creation: {child}; Runtime: {:?}",
runtime_errors.lock().unwrap()
);
let child_ref = RuntimeWorkerRef::new(
child["runtime_id"].as_str().unwrap(),
child["worker_id"].as_str().unwrap(),
);
let grant = subject_store
.worker_execution_grant(&child_ref.runtime_id, &child_ref.worker_id)
.unwrap()
.unwrap();
assert_eq!(grant.origin_worker_id, source.worker_id);
assert_eq!(grant.permissions, child_permissions);
assert!(grant.redelegation.is_empty());
assert!(
api.store
.require_current_worker_singleton_owner(TEST_WORKSPACE_ID, &child_ref)
.unwrap()
.is_none(),
"ordinary Profile child must not become a Subject body"
);
let (status, replay) =
connected_authorization_post(&backend_url, &identity, &source, &path, &wire).await;
assert_eq!(status, StatusCode::OK, "stable operation replay: {replay}");
assert_eq!(replay["worker_id"], child["worker_id"]);
let results = tokio::time::timeout(std::time::Duration::from_secs(30), completion.recv())
.await
.expect("real child Controller must return both tool results")
.expect("scripted model completion channel");
let read = results
.iter()
.find(|(id, _, _)| id == "connected-read")
.unwrap();
assert!(
!read.1,
"actual Read must succeed under the read-only grant: {read:?}"
);
assert!(
read.2.contains("clean"),
"Read must return materialized repository content: {read:?}"
);
let write = results
.iter()
.find(|(id, _, _)| id == "connected-write")
.unwrap();
assert!(
write.1,
"editing Profile must not enlarge the read-only grant: {write:?}"
);
assert!(
write.2.contains("Workdir session does not support Write"),
"the Host-attenuated attachment must deny before provider dispatch: {write:?}"
);
// The actual Write is refused even before provider dispatch by the narrowed
// attachment capabilities. Independently query the same child's live Host
// policy, rather than claiming that a refused Tool reached the write adapter.
let mut denied_write = read_permission.clone();
denied_write.operation = "filesystem.write".into();
let (status, body) = connected_authorization_post(
&backend_url,
&identity,
&child_ref,
&authorization_path(),
&serde_json::to_value(denied_write).unwrap(),
)
.await;
assert_eq!(status, StatusCode::FORBIDDEN, "{body}");
assert_eq!(
fs::read_to_string(&readme).unwrap(),
"clean\n",
"denied Write has no effect on the actual Runtime materialization"
);
let observations = authorizations.lock().unwrap();
for (operation, expected) in [
("filesystem.read", StatusCode::OK),
("filesystem.write", StatusCode::FORBIDDEN),
] {
assert!(
observations
.iter()
.any(|(permission, status)| permission["operation"] == operation
&& permission["resource"]["working_directory_id"] == workdir_id
&& *status == expected),
"signed Host authorization must observe {operation} with {expected}: {observations:?}"
);
}
assert!(
browser_ticket_backend(&api)
.unwrap()
.list(Default::default())
.unwrap()
.is_empty(),
"generic Profile delegation requires no Ticket"
);
}
+7 -1
View File
@@ -18155,7 +18155,7 @@
"info": {
"title": "Yoi Server API",
"version": "0.1.0",
"x-yoi-source-digest": "sha256:7c4ecff64866c760633454bd9ed66fcf25e16956a5152b97ab91576bb79fe083"
"x-yoi-source-digest": "sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7"
},
"openapi": "3.1.0",
"paths": {
@@ -35399,6 +35399,9 @@
}
},
"security": [
{
"bearerAuth": []
},
{
"browserSession": []
}
@@ -35511,6 +35514,9 @@
}
},
"security": [
{
"bearerAuth": []
},
{
"browserSession": []
}
@@ -1,6 +1,6 @@
// @generated by yoi-companion-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:7c4ecff64866c760633454bd9ed66fcf25e16956a5152b97ab91576bb79fe083)
// Input contract: 0.1.0 (sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7)
// Options: companion-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/companion-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_companion_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-repository-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:7c4ecff64866c760633454bd9ed66fcf25e16956a5152b97ab91576bb79fe083)
// Input contract: 0.1.0 (sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7)
// Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output
// Output: web/workspace/src/lib/generated/repository-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-runtime-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:7c4ecff64866c760633454bd9ed66fcf25e16956a5152b97ab91576bb79fe083)
// Input contract: 0.1.0 (sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7)
// Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/runtime-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-ticket-browser-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:7c4ecff64866c760633454bd9ed66fcf25e16956a5152b97ab91576bb79fe083)
// Input contract: 0.1.0 (sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7)
// Options: ticket-objective-merge-request-browser-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/ticket-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_ticket_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-workdir-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:7c4ecff64866c760633454bd9ed66fcf25e16956a5152b97ab91576bb79fe083)
// Input contract: 0.1.0 (sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7)
// Options: workdir-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/workdir-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_workdir_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-worker-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:7c4ecff64866c760633454bd9ed66fcf25e16956a5152b97ab91576bb79fe083)
// Input contract: 0.1.0 (sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7)
// Options: worker-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/worker-launch-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_worker_launch_api_types -- --check